From 880eb28e9f5f6b7a34e79f38117cf1d0b5fbcfb3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Mar 2018 22:41:34 +0200 Subject: [PATCH 01/18] Use double quotes --- 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 98cf5e65..ef3e0f26 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -2293,7 +2293,7 @@ class Client: ) if isinstance(r, types.upload.File): - with tempfile.NamedTemporaryFile('wb', delete=False) as f: + with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name while True: @@ -2332,7 +2332,7 @@ class Client: cdn_session.start() try: - with tempfile.NamedTemporaryFile('wb', delete=False) as f: + with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name while True: From 387bbbf090b53995aaededa1970ca06880562540 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 1 Apr 2018 17:38:22 +0200 Subject: [PATCH 02/18] Add new force_sms parameter to force Telegram sending the code via SMS --- pyrogram/client/client.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index ef3e0f26..07869a62 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -136,6 +136,7 @@ class Client: phone_number: str = None, phone_code: str or callable = None, password: str = None, + force_sms: bool = False, first_name: str = None, last_name: str = None, workers: int = 4): @@ -149,6 +150,7 @@ class Client: self.phone_code = phone_code self.first_name = first_name self.last_name = last_name + self.force_sms = force_sms self.workers = workers @@ -346,6 +348,14 @@ class Client: phone_registered = r.phone_registered phone_code_hash = r.phone_code_hash + if self.force_sms: + self.send( + functions.auth.ResendCode( + phone_number=self.phone_number, + phone_code_hash=phone_code_hash + ) + ) + while True: self.phone_code = ( input("Enter phone code: ") if self.phone_code is None From fecea07db68d692f5f7bad850fae555dab316b04 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 1 Apr 2018 18:17:20 +0200 Subject: [PATCH 03/18] Document force_sms parameter --- pyrogram/client/client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 07869a62..4f1ac855 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -110,6 +110,10 @@ class Client: Pass your Two-Step Verification password (if you have one) to avoid entering it manually. Only applicable for new sessions. + force_sms (``str``, optional): + Pass True to force Telegram sending the authorization code via SMS. + Only applicable for new sessions. + first_name (``str``, optional): Pass a First Name to avoid entering it manually. It will be used to automatically create a new Telegram account in case the phone number you passed is not registered yet. From 21ab5295c4077d42518df64c250d00a8bb309ab1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 1 Apr 2018 18:18:06 +0200 Subject: [PATCH 04/18] Update first_name and last_name parameters' docs --- pyrogram/client/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 4f1ac855..23a1568b 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -117,10 +117,11 @@ class Client: first_name (``str``, optional): Pass a First Name to avoid entering it manually. It will be used to automatically create a new Telegram account in case the phone number you passed is not registered yet. + Only applicable for new sessions. last_name (``str``, optional): Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can - be an empty string: "" + be an empty string: "". Only applicable for new sessions. workers (``int``, optional): Thread pool size for handling incoming updates. Defaults to 4. From 1849c26b5e062a6478f02bf0f733c441329ea8a4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Apr 2018 10:02:30 +0200 Subject: [PATCH 05/18] Revert "Remove old code and use a better error message" This reverts commit 7f13eef --- pyrogram/crypto/aes.py | 57 +++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py index 8ca72535..05a01044 100644 --- a/pyrogram/crypto/aes.py +++ b/pyrogram/crypto/aes.py @@ -16,33 +16,52 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import logging + +log = logging.getLogger(__name__) + try: import tgcrypto -except ImportError as e: - e.msg = ( - "TgCrypto is missing and Pyrogram can't run without. " - "Please install it using \"pip3 install tgcrypto\". " +except ImportError: + log.warning( + "TgCrypto is missing! " + "Pyrogram will work the same, but at a much slower speed. " "More info: https://docs.pyrogram.ml/resources/TgCrypto" ) - - raise e + is_fast = False + import pyaes +else: + log.info("Using TgCrypto") + is_fast = True +# TODO: Ugly IFs class AES: @classmethod def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: - return tgcrypto.ige_encrypt(data, key, iv) + if is_fast: + return tgcrypto.ige_encrypt(data, key, iv) + else: + return cls.ige(data, key, iv, True) @classmethod def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: - return tgcrypto.ige_decrypt(data, key, iv) + if is_fast: + return tgcrypto.ige_decrypt(data, key, iv) + else: + return cls.ige(data, key, iv, False) @staticmethod def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes: replace = int.to_bytes(offset // 16, 4, "big") iv = iv[:-4] + replace - return tgcrypto.ctr_decrypt(data, key, iv) + if is_fast: + return tgcrypto.ctr_decrypt(data, key, iv) + else: + ctr = pyaes.AESModeOfOperationCTR(key) + ctr._counter._counter = list(iv) + return ctr.decrypt(data) @staticmethod def xor(a: bytes, b: bytes) -> bytes: @@ -51,3 +70,23 @@ class AES: len(a), "big", ) + + @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:] + + 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] = 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) From 5f0ab753efd70b758d95db3b1483e9811c68db22 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Apr 2018 11:04:39 +0200 Subject: [PATCH 06/18] Update requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 21c697f1..3216c15d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -pysocks -tgcrypto \ No newline at end of file +pyaes>=1.6.1 +pysocks>=1.6.8 \ No newline at end of file From 8b3ba8a5733584cf471d973f41782eda35f49197 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Apr 2018 11:04:59 +0200 Subject: [PATCH 07/18] Add requirements_extras.txt --- requirements_extras.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 requirements_extras.txt diff --git a/requirements_extras.txt b/requirements_extras.txt new file mode 100644 index 00000000..1d101a7e --- /dev/null +++ b/requirements_extras.txt @@ -0,0 +1 @@ +tgcrypto>=1.0.4 \ No newline at end of file From d1517ae8857792aa00e98fc8bc2e29b576085fe0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Apr 2018 11:07:23 +0200 Subject: [PATCH 08/18] Update setup.py --- setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index c82c1a9d..7a8e0132 100644 --- a/setup.py +++ b/setup.py @@ -25,8 +25,8 @@ from compiler.api import compiler as api_compiler from compiler.error import compiler as error_compiler -def requirements(): - with open("requirements.txt", encoding="utf-8") as r: +def read(file: str) -> list: + with open(file, encoding="utf-8") as r: return [i.strip() for i in r] @@ -82,5 +82,6 @@ setup( python_requires="~=3.4", packages=find_packages(exclude=["compiler*"]), zip_safe=False, - install_requires=requirements() + install_requires=read("requirements.txt"), + extras_require={"tgcrypto": read("requirements_extras.txt")} ) From e6ee76792bd59a5f180a052e480d41372a9e185b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Apr 2018 11:07:58 +0200 Subject: [PATCH 09/18] Add requirements_extras.txt to MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index f818e13a..a1d19d94 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ ## Include -include COPYING COPYING.lesser NOTICE requirements.txt +include COPYING COPYING.lesser NOTICE requirements.txt requirements_extras.txt recursive-include compiler *.py *.tl *.tsv *.txt ## Exclude From b5304ca23ac197e7e07400245a3d1074eaa9d07e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Apr 2018 11:11:38 +0200 Subject: [PATCH 10/18] Use fully qualified channel id --- 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 23a1568b..005fda4f 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -649,7 +649,7 @@ class Client: if not isinstance(message, types.MessageEmpty): diff = self.send( functions.updates.GetChannelDifference( - channel=self.resolve_peer(update.message.to_id.channel_id), + channel=self.resolve_peer(int("-100" + str(update.message.to_id.channel_id))), filter=types.ChannelMessagesFilter( ranges=[types.MessageRange( min_id=update.message.id, From 2f2a381686150f60caf7adb9e66efdc73ae683e9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Apr 2018 12:14:22 +0200 Subject: [PATCH 11/18] Add extra GetDialogs step --- pyrogram/client/client.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 005fda4f..1da96844 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -914,6 +914,15 @@ class Client: offset_date = parse_dialogs(dialogs) log.info("Entities count: {}".format(len(self.peers_by_id))) + self.send( + functions.messages.GetDialogs( + 0, 0, types.InputPeerEmpty(), + self.DIALOGS_AT_ONCE, True + ) + ) + + log.info("Entities count: {}".format(len(self.peers_by_id))) + def resolve_peer(self, peer_id: int or str): """Use this method to get the *InputPeer* of a known *peer_id*. From e69fea4bb577c542403e9680c3cc25615b90b997 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 3 Apr 2018 11:40:08 +0200 Subject: [PATCH 12/18] More readable exception handling --- pyrogram/client/client.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1da96844..237b00f7 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -802,15 +802,15 @@ class Client: Raises: :class:`Error ` """ - if self.is_started: - r = self.session.send(data) + if not self.is_started: + raise ConnectionError("Client has not been started") - self.fetch_peers(getattr(r, "users", [])) - self.fetch_peers(getattr(r, "chats", [])) + r = self.session.send(data) - return r - else: - raise ConnectionError("client '{}' is not started".format(self.session_name)) + self.fetch_peers(getattr(r, "users", [])) + self.fetch_peers(getattr(r, "chats", [])) + + return r def load_config(self): parser = ConfigParser() From 10452dc545cc455f4e10d2d1e6948f2fb9b4828b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 3 Apr 2018 11:45:19 +0200 Subject: [PATCH 13/18] Don't allow start() to be called more than once --- pyrogram/client/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 237b00f7..08e0e117 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -194,6 +194,9 @@ class Client: Raises: :class:`Error ` """ + if self.is_started: + raise ConnectionError("Client has already been started") + if self.BOT_TOKEN_RE.match(self.session_name): self.token = self.session_name self.session_name = self.session_name.split(":")[0] From fcf0e4515f9ac99d7b8168890ba946e08740153b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 3 Apr 2018 14:54:34 +0200 Subject: [PATCH 14/18] Don't try to stop a non-started Client --- pyrogram/client/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 08e0e117..be04db75 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -245,6 +245,9 @@ class Client: """Use this method to manually stop the Client. Requires no parameters. """ + if not self.is_started: + raise ConnectionError("Client is already stopped") + self.is_started = False self.session.stop() From f8b272a9255f70d3158049152f488bf7fafe3118 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 5 Apr 2018 11:31:01 +0200 Subject: [PATCH 15/18] Allow passing phone numbers with white spaces E.g.: "+39 123 456 7890" --- 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 be04db75..a7a91f93 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -961,7 +961,7 @@ class Client: except (AttributeError, binascii.Error, struct.error): pass - peer_id = peer_id.lower().strip("@+") + peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) try: int(peer_id) From cce937e54b616a2e9a901789839053dcc62f5363 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 5 Apr 2018 11:43:56 +0200 Subject: [PATCH 16/18] Set correct type hint --- 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 46d722fc..5d54ff1f 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -87,7 +87,7 @@ class Session: test_mode: bool, proxy: type, auth_key: bytes, - api_id: str, + api_id: int, is_cdn: bool = False, client: pyrogram = None): if not Session.notice_displayed: From 942c20d08b7189ea1ae1f0190bff029b605ffd63 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 5 Apr 2018 12:55:34 +0200 Subject: [PATCH 17/18] Use separate api_id and api_hash parameters Instead of a tuple (api_id, api_hash) --- pyrogram/client/client.py | 61 ++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index a7a91f93..ccadfb87 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -81,10 +81,13 @@ class Client: For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number. - api_key (``tuple``, optional): - Your Telegram API Key as tuple: *(api_id, api_hash)*. - E.g.: *(12345, "0123456789abcdef0123456789abcdef")*. This is an alternative way to pass it if you - don't want to use the *config.ini* file. + api_id (``int``, 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 pass it if you don't want to use the *config.ini* file. proxy (``dict``, optional): Your SOCKS5 Proxy settings as dict, @@ -135,7 +138,8 @@ class Client: def __init__(self, session_name: str, - api_key: tuple or APIKey = None, + api_id: int = None, + api_hash: str = None, proxy: dict or Proxy = None, test_mode: bool = False, phone_number: str = None, @@ -146,7 +150,8 @@ class Client: last_name: str = None, workers: int = 4): self.session_name = session_name - self.api_key = api_key + self.api_id = api_id + self.api_hash = api_hash self.proxy = proxy self.test_mode = test_mode @@ -209,7 +214,7 @@ class Client: self.test_mode, self.proxy, self.auth_key, - self.api_key.api_id, + self.api_id, client=self ) @@ -265,8 +270,8 @@ class Client: r = self.send( functions.auth.ImportBotAuthorization( flags=0, - api_id=self.api_key.api_id, - api_hash=self.api_key.api_hash, + api_id=self.api_id, + api_hash=self.api_hash, bot_auth_token=self.token ) ) @@ -281,7 +286,7 @@ class Client: self.test_mode, self.proxy, self.auth_key, - self.api_key.api_id, + self.api_id, client=self ) @@ -314,8 +319,8 @@ class Client: r = self.send( functions.auth.SendCode( self.phone_number, - self.api_key.api_id, - self.api_key.api_hash + self.api_id, + self.api_hash ) ) except (PhoneMigrate, NetworkMigrate) as e: @@ -329,7 +334,7 @@ class Client: self.test_mode, self.proxy, self.auth_key, - self.api_key.api_id, + self.api_id, client=self ) self.session.start() @@ -337,8 +342,8 @@ class Client: r = self.send( functions.auth.SendCode( self.phone_number, - self.api_key.api_id, - self.api_key.api_hash + self.api_id, + self.api_hash ) ) break @@ -822,18 +827,14 @@ class Client: parser = ConfigParser() parser.read("config.ini") - if self.api_key is not None: - self.api_key = APIKey( - api_id=int(self.api_key[0]), - api_hash=self.api_key[1] - ) - elif parser.has_section("pyrogram"): - self.api_key = APIKey( - api_id=parser.getint("pyrogram", "api_id"), - api_hash=parser.get("pyrogram", "api_hash") - ) + if self.api_id and self.api_hash: + pass else: - raise AttributeError("No API Key found") + 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") if self.proxy is not None: self.proxy = Proxy( @@ -2191,7 +2192,7 @@ class Client: 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.dc_id, self.test_mode, self.proxy, self.auth_key, self.api_key.api_id) + session = Session(self.dc_id, self.test_mode, self.proxy, self.auth_key, self.api_id) session.start() try: @@ -2274,7 +2275,7 @@ class Client: self.test_mode, self.proxy, Auth(dc_id, self.test_mode, self.proxy).create(), - self.api_key.api_id + self.api_id ) session.start() @@ -2291,7 +2292,7 @@ class Client: self.test_mode, self.proxy, self.auth_key, - self.api_key.api_id + self.api_id ) session.start() @@ -2355,7 +2356,7 @@ class Client: self.test_mode, self.proxy, Auth(r.dc_id, self.test_mode, self.proxy).create(), - self.api_key.api_id, + self.api_id, is_cdn=True ) From 73fbe600573fee4d0f77b2cd1bd83c245d3d7456 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 5 Apr 2018 13:05:27 +0200 Subject: [PATCH 18/18] Remove APIKey class --- pyrogram/client/client.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index ccadfb87..31b4b895 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -54,12 +54,6 @@ from .style import Markdown, HTML log = logging.getLogger(__name__) -class APIKey: - def __init__(self, api_id: int, api_hash: str): - self.api_id = api_id - self.api_hash = api_hash - - class Proxy: def __init__(self, enabled: bool, hostname: str, port: int, username: str, password: str): self.enabled = enabled