From 5fc61e2fc96b0448513371a4764a4690a2a84a5a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 14 Mar 2018 12:03:10 +0100 Subject: [PATCH 01/19] Update to v0.6.3 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index b2b85d0c..084cce6e 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Thu, 15 Mar 2018 09:50:37 +0100 Subject: [PATCH 02/19] Add get_participants2.py --- examples/README.md | 1 + examples/get_participants2.py | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 examples/get_participants2.py diff --git a/examples/README.md b/examples/README.md index 6f640ef4..545516fa 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,6 +8,7 @@ you have to change are the target chats (username, id) and file paths for sendin - [**hello_world.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/hello_world.py) - [**get_history.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/get_history.py) - [**get_participants.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/get_participants.py) +- [**get_participants2.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/get_participants2.py) - [**inline_bots.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/inline_bots.py) - [**updates.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/updates.py) - [**simple_echo.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/simple_echo.py) diff --git a/examples/get_participants2.py b/examples/get_participants2.py new file mode 100644 index 00000000..23ed328f --- /dev/null +++ b/examples/get_participants2.py @@ -0,0 +1,63 @@ +import time +from string import ascii_lowercase + +from pyrogram import Client +from pyrogram.api import functions, types +from pyrogram.api.errors import FloodWait + +""" +This is an improved version of get_participants.py + +Since Telegram will return at most 10.000 users for a single query, this script +repeats the search using numbers ("0" to "9") and all the available ascii letters ("a" to "z"). + +This can be further improved by also searching for non-ascii characters (e.g.: Japanese script), +as some user names may not contain ascii letters at all. +""" + +client = Client("example") +client.start() + +target = "username" # Target channel/supergroup username or id +users = {} # To ensure uniqueness, users will be stored in a dictionary with user_id as key +limit = 200 # Amount of users to retrieve for each API call (200 is the maximum) + +# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list) +queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase) + +for q in queries: + print("Searching for '{}'".format(q)) + offset = 0 # For each query, offset restarts from 0 + + while True: + try: + participants = client.send( + functions.channels.GetParticipants( + channel=client.resolve_peer(target), + filter=types.ChannelParticipantsSearch(q), + offset=offset, + limit=limit, + hash=0 + ) + ) + except FloodWait as e: + # Very large chats could trigger FloodWait. + # When happens, wait X seconds before continuing + print("Flood wait: {} seconds".format(e.x)) + time.sleep(e.x) + continue + + if not participants.participants: + print("Done searching for '{}'".format(q)) + print() + break # No more participants left + + # User information are stored in the participants.users list. + # Add those users to the dictionary + users.update({i.id: i for i in participants.users}) + + offset += len(participants.participants) + + print("Total users: {}".format(len(users))) + +client.stop() From 756311710c77ba43409ef94f24df51c488d4a44b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 15 Mar 2018 12:03:02 +0100 Subject: [PATCH 03/19] Don't GetTermsOfService() anymore --- pyrogram/session/session.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 6225fe44..c1269d49 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -122,8 +122,6 @@ class Session: self.is_connected = Event() def start(self): - terms = None - while True: try: self.connection.connect() @@ -141,7 +139,7 @@ class Session: self.next_salt_thread.start() if not self.is_cdn: - terms = self._send( + self._send( functions.InvokeWithLayer( layer, functions.InitConnection( @@ -150,10 +148,10 @@ class Session: self.SYSTEM_VERSION, self.APP_VERSION, "en", "", "en", - functions.help.GetTermsOfService(), + functions.help.GetConfig(), ) ) - ).text + ) self.ping_thread = Thread(target=self.ping, name="PingThread") self.ping_thread.start() @@ -168,8 +166,6 @@ class Session: log.debug("Session started") - return terms - def stop(self): self.is_connected.clear() From 6d536107aeb8333cbf45c78d6cf4dedfbc7b72a5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 15 Mar 2018 12:18:48 +0100 Subject: [PATCH 04/19] Add support for bots login via token --- pyrogram/client/client.py | 374 +++++++++++++++++++++----------------- 1 file changed, 205 insertions(+), 169 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 6c47ec68..410e167c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -41,7 +41,7 @@ from pyrogram.api.errors import ( PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PasswordHashInvalid, FloodWait, PeerIdInvalid, FilePartMissing, ChatAdminRequired, FirstnameInvalid, PhoneNumberBanned, - VolumeLocNotFound) + VolumeLocNotFound, UserMigrate) from pyrogram.api.types import ( User, Chat, Channel, PeerUser, PeerChannel, @@ -123,6 +123,7 @@ class Client: api_key: tuple or ApiKey = None, proxy: dict or Proxy = None, test_mode: bool = False, + token: str = None, phone_number: str = None, phone_code: str or callable = None, password: str = None, @@ -134,6 +135,7 @@ class Client: self.proxy = proxy self.test_mode = test_mode + self.token = token self.phone_number = phone_number self.password = password self.phone_code = phone_code @@ -186,18 +188,21 @@ class Client: client=self ) - terms = self.session.start() + self.session.start() if self.user_id is None: - print("\n".join(terms.splitlines()), "\n") + if self.token is None: + self.authorize_user() + else: + self.authorize_bot() - self.user_id = self.authorize() - self.password = None self.save_session() + if self.token is None: + self.get_dialogs() + self.get_contacts() + self.rnd_id = MsgId - self.get_dialogs() - self.get_contacts() for i in range(self.UPDATES_WORKERS): Thread(target=self.updates_worker, name="UpdatesWorker#{}".format(i + 1)).start() @@ -225,6 +230,199 @@ class Client: for _ in range(self.DOWNLOAD_WORKERS): self.download_queue.put(None) + def authorize_bot(self): + try: + r = self.send( + functions.auth.ImportBotAuthorization( + flags=0, + api_id=self.api_key.api_id, + api_hash=self.api_key.api_hash, + bot_auth_token=self.token + ) + ) + except UserMigrate as e: + self.session.stop() + + self.dc_id = e.x + self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create() + + self.session = Session( + self.dc_id, + self.test_mode, + self.proxy, + self.auth_key, + self.api_key.api_id, + client=self + ) + + self.session.start() + self.authorize_bot() + else: + self.user_id = r.user.id + + 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 + first_name_invalid_raises = self.first_name is not None + + while True: + if self.phone_number is None: + self.phone_number = input("Enter phone number: ") + + while True: + confirm = input("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 = self.phone_number.strip("+") + + try: + r = self.send( + functions.auth.SendCode( + self.phone_number, + self.api_key.api_id, + self.api_key.api_hash + ) + ) + except (PhoneMigrate, NetworkMigrate) as e: + self.session.stop() + + self.dc_id = e.x + self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create() + + self.session = Session( + self.dc_id, + self.test_mode, + self.proxy, + self.auth_key, + self.api_key.api_id, + client=self + ) + self.session.start() + + r = self.send( + functions.auth.SendCode( + self.phone_number, + self.api_key.api_id, + self.api_key.api_hash + ) + ) + break + except (PhoneNumberInvalid, PhoneNumberBanned) as e: + if phone_number_invalid_raises: + raise + else: + print(e.MESSAGE) + self.phone_number = None + 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) + else: + break + + phone_registered = r.phone_registered + phone_code_hash = r.phone_code_hash + + while True: + self.phone_code = ( + input("Enter phone code: ") if self.phone_code is None + else self.phone_code if type(self.phone_code) is str + else self.phone_code() + ) + + try: + if phone_registered: + r = self.send( + functions.auth.SignIn( + self.phone_number, + phone_code_hash, + self.phone_code + ) + ) + else: + try: + self.send( + functions.auth.SignIn( + self.phone_number, + phone_code_hash, + self.phone_code + ) + ) + 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: ") + + r = self.send( + functions.auth.SignUp( + self.phone_number, + phone_code_hash, + self.phone_code, + self.first_name, + self.last_name + ) + ) + except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e: + if phone_code_invalid_raises: + raise + else: + print(e.MESSAGE) + self.phone_code = None + except FirstnameInvalid as e: + if first_name_invalid_raises: + raise + else: + print(e.MESSAGE) + self.first_name = None + except SessionPasswordNeeded as e: + print(e.MESSAGE) + r = self.send(functions.account.GetPassword()) + + while True: + try: + + if self.password is None: + print("Hint: {}".format(r.hint)) + self.password = input("Enter password: ") # TODO: Use getpass + + if type(self.password) is str: + self.password = r.current_salt + self.password.encode() + r.current_salt + + password_hash = sha256(self.password).digest() + + r = self.send(functions.auth.CheckPassword(password_hash)) + except PasswordHashInvalid as e: + if password_hash_invalid_raises: + raise + else: + print(e.MESSAGE) + self.password = None + 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) + else: + break + break + 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) + else: + break + + self.password = None + self.user_id = r.user.id + def fetch_peers(self, entities: list): for entity in entities: if isinstance(entity, User): @@ -558,168 +756,6 @@ class Client: return r - def authorize(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 - first_name_invalid_raises = self.first_name is not None - - while True: - if self.phone_number is None: - self.phone_number = input("Enter phone number: ") - - while True: - confirm = input("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 = self.phone_number.strip("+") - - try: - r = self.send( - functions.auth.SendCode( - self.phone_number, - self.api_key.api_id, - self.api_key.api_hash - ) - ) - except (PhoneMigrate, NetworkMigrate) as e: - self.session.stop() - - self.dc_id = e.x - self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create() - - self.session = Session( - self.dc_id, - self.test_mode, - self.proxy, - self.auth_key, - self.api_key.api_id, - client=self - ) - self.session.start() - - r = self.send( - functions.auth.SendCode( - self.phone_number, - self.api_key.api_id, - self.api_key.api_hash - ) - ) - break - except (PhoneNumberInvalid, PhoneNumberBanned) as e: - if phone_number_invalid_raises: - raise - else: - print(e.MESSAGE) - self.phone_number = None - 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) - else: - break - - phone_registered = r.phone_registered - phone_code_hash = r.phone_code_hash - - while True: - self.phone_code = ( - input("Enter phone code: ") if self.phone_code is None - else self.phone_code if type(self.phone_code) is str - else self.phone_code() - ) - - try: - if phone_registered: - r = self.send( - functions.auth.SignIn( - self.phone_number, - phone_code_hash, - self.phone_code - ) - ) - else: - try: - self.send( - functions.auth.SignIn( - self.phone_number, - phone_code_hash, - self.phone_code - ) - ) - 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: ") - - r = self.send( - functions.auth.SignUp( - self.phone_number, - phone_code_hash, - self.phone_code, - self.first_name, - self.last_name - ) - ) - except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e: - if phone_code_invalid_raises: - raise - else: - print(e.MESSAGE) - self.phone_code = None - except FirstnameInvalid as e: - if first_name_invalid_raises: - raise - else: - print(e.MESSAGE) - self.first_name = None - except SessionPasswordNeeded as e: - print(e.MESSAGE) - r = self.send(functions.account.GetPassword()) - - while True: - try: - - if self.password is None: - print("Hint: {}".format(r.hint)) - self.password = input("Enter password: ") # TODO: Use getpass - - if type(self.password) is str: - self.password = r.current_salt + self.password.encode() + r.current_salt - - password_hash = sha256(self.password).digest() - - r = self.send(functions.auth.CheckPassword(password_hash)) - except PasswordHashInvalid as e: - if password_hash_invalid_raises: - raise - else: - print(e.MESSAGE) - self.password = None - 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) - else: - break - break - 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) - else: - break - - return r.user.id - def load_config(self): parser = ConfigParser() parser.read("config.ini") From 839f7b99f43631d8e760e69d6074977c7945d014 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 15 Mar 2018 20:41:13 +0100 Subject: [PATCH 05/19] Call GetState for bots --- pyrogram/client/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 410e167c..39ed55d1 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -201,6 +201,8 @@ class Client: if self.token is None: self.get_dialogs() self.get_contacts() + else: + self.send(functions.updates.GetState()) self.rnd_id = MsgId From 4dbf1ef5ac0e8826ee7d0dccddcf276dc9ac071d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 16 Mar 2018 11:18:16 +0100 Subject: [PATCH 06/19] Improve re-connection speed --- pyrogram/session/session.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index c1269d49..333947e4 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -186,6 +186,9 @@ class Session: for i in range(self.NET_WORKERS): self.recv_queue.put(None) + for i in self.results.values(): + i.event.set() + log.debug("Session stopped") def restart(self): From 6fd8b582b21419b2daf3d20a1289779315b4af16 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 16 Mar 2018 11:30:05 +0100 Subject: [PATCH 07/19] More useful logging --- 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 333947e4..46d722fc 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -400,7 +400,7 @@ class Session: try: return self._send(data) except (OSError, TimeoutError): - log.warning("Retrying {}".format(type(data))) + (log.warning if i > 0 else log.info)("{}: {} Retrying {}".format(i, datetime.now(), type(data))) continue else: return None From 6c206616860657ddc25f831319632d7d59072ad0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 17 Mar 2018 14:29:23 +0100 Subject: [PATCH 08/19] Don't use kwargs --- pyrogram/crypto/aes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py index c571fb2d..05a01044 100644 --- a/pyrogram/crypto/aes.py +++ b/pyrogram/crypto/aes.py @@ -53,7 +53,7 @@ class AES: @staticmethod def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes: - replace = int.to_bytes(offset // 16, byteorder="big", length=4) + replace = int.to_bytes(offset // 16, 4, "big") iv = iv[:-4] + replace if is_fast: From 600e705d51e1aff97de1ed57a76c79e9f1f0c56b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 17 Mar 2018 19:05:18 +0100 Subject: [PATCH 09/19] Add docstrings for "token" parameter --- pyrogram/client/client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 39ed55d1..d71534ed 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -88,6 +88,10 @@ class Client: Only applicable for new sessions and will be ignored in case previously created sessions are loaded. + token (:obj:`str`, optional): + Pass your Bot API token to log-in as Bot. + E.g.: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 + phone_number (:obj:`str`, optional): Pass your phone number (with your Country Code prefix included) to avoid entering it manually. Only applicable for new sessions. From 1da39efa2e5aaf87afbbb92bcbd14d12704ac6e6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 17 Mar 2018 19:13:15 +0100 Subject: [PATCH 10/19] Update invite link regex --- 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 d71534ed..ab0d4ab7 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -117,7 +117,7 @@ class Client: Thread pool size for handling incoming updates. Defaults to 4. """ - INVITE_LINK_RE = re.compile(r"^(?:https?://)?t\.me/joinchat/(.+)$") + INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)?(.+)$") DIALOGS_AT_ONCE = 100 UPDATES_WORKERS = 2 DOWNLOAD_WORKERS = 1 From b45f2f4595a2b21e9d45d4708b88a6514bd4e728 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 17 Mar 2018 19:24:27 +0100 Subject: [PATCH 11/19] Add support for sending messages using joinchat links and hashes --- pyrogram/client/client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index ab0d4ab7..a9aa2e9c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -23,6 +23,7 @@ import math import mimetypes import os import re +import struct import threading import time from collections import namedtuple @@ -916,6 +917,12 @@ class Client: if peer_id in ("self", "me"): return InputPeerSelf() + match = self.INVITE_LINK_RE.match(peer_id) + + if match: + decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), altchars="-_") + return self.resolve_peer(struct.unpack(">2iq", decoded)[1]) + peer_id = peer_id.lower().strip("@+") try: From 1d25b84cde1ab9c1510ea8b6813248d7232fda3f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 18 Mar 2018 11:43:51 +0100 Subject: [PATCH 12/19] Update invite link regex pattern --- 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 a9aa2e9c..4cef260d 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -118,7 +118,7 @@ class Client: Thread pool size for handling incoming updates. Defaults to 4. """ - INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)?(.+)$") + INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)?([\w-]+)$") DIALOGS_AT_ONCE = 100 UPDATES_WORKERS = 2 DOWNLOAD_WORKERS = 1 From cbd3b71b7993f175b966a33e7440542fda61170a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 18 Mar 2018 12:12:27 +0100 Subject: [PATCH 13/19] Handle mismatches in a more pythonic way --- pyrogram/client/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 4cef260d..820c7dd1 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import base64 +import binascii import json import logging import math @@ -919,9 +920,11 @@ class Client: match = self.INVITE_LINK_RE.match(peer_id) - if match: - decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), altchars="-_") + try: + decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), "-_") return self.resolve_peer(struct.unpack(">2iq", decoded)[1]) + except (AttributeError, binascii.Error, struct.error): + pass peer_id = peer_id.lower().strip("@+") From 5b5fb6cbec6ea083418b7485fc766f18181b698c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 18 Mar 2018 13:00:28 +0100 Subject: [PATCH 14/19] Update docstrings to accommodate joinchat links --- pyrogram/client/client.py | 141 +++++++++++++++++++++----------------- 1 file changed, 80 insertions(+), 61 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 820c7dd1..9c49058a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -986,9 +986,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. text (:obj:`str`): Text of the message to be sent. @@ -1036,15 +1037,16 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. from_chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the chat where the original message was sent - (or channel/supergroup username in the format @username). For your personal cloud - storage (Saved Messages) you can simply use "me" or "self". - Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. message_ids (:obj:`list`): A list of Message identifiers in the chat specified in *from_chat_id*. @@ -1082,9 +1084,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. photo (:obj:`str`): Photo to send. @@ -1167,9 +1170,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. audio (:obj:`str`): Audio file to send. @@ -1259,9 +1263,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. document (:obj:`str`): File to send. @@ -1335,9 +1340,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. sticker (:obj:`str`): Sticker to send. @@ -1409,9 +1415,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. video (:obj:`str`): Video to send. @@ -1513,9 +1520,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. voice (:obj:`str`): Audio file to send. @@ -1597,9 +1605,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. video_note (:obj:`str`): Video note to send. @@ -1676,9 +1685,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. media (:obj:`list`): A list containing either :obj:`pyrogram.InputMedia.Photo` or :obj:`pyrogram.InputMedia.Video` objects @@ -1774,9 +1784,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. latitude (:obj:`float`): Latitude of the location. @@ -1826,9 +1837,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. latitude (:obj:`float`): Latitude of the venue. @@ -1890,9 +1902,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. phone_number (:obj:`str`): Contact's phone number. @@ -1939,9 +1952,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. action (:obj:`callable`): Type of action to broadcast. @@ -2001,9 +2015,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. message_id (:obj:`int`): Message identifier in the chat specified in chat_id. @@ -2042,9 +2057,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. message_id (:obj:`int`): Message identifier in the chat specified in chat_id. @@ -2084,9 +2100,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. message_ids (:obj:`list`): List of identifiers of the messages to delete. @@ -2823,9 +2840,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. query_id (:obj:`int`): Unique identifier for the answered query. @@ -2865,9 +2883,10 @@ class Client: Args: chat_id (:obj:`int` | :obj:`str`): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). For your personal cloud storage (Saved Messages) you can - simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported. + 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). + For a private channel/supergroup you can use its *t.me/joinchat/* link. message_ids (:obj:`list`): A list of Message identifiers in the chat specified in *chat_id*. From 70ae7f0808532fa5a02eb5cafc9bbd94679ce8d2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 19 Mar 2018 01:40:36 +0100 Subject: [PATCH 15/19] Fix usernames not stored in lowercase --- 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 9c49058a..e3968b31 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -455,7 +455,7 @@ class Client: self.peers_by_id[user_id] = input_peer if username is not None: - self.peers_by_username[username] = input_peer + self.peers_by_username[username.lower()] = input_peer if phone is not None: self.peers_by_phone[phone] = input_peer @@ -495,7 +495,7 @@ class Client: self.peers_by_id[peer_id] = input_peer if username is not None: - self.peers_by_username[username] = input_peer + self.peers_by_username[username.lower()] = input_peer def download_worker(self): name = threading.current_thread().name @@ -889,7 +889,7 @@ class Client: else: raise PeerIdInvalid - self.peers_by_username[username] = input_peer + self.peers_by_username[username.lower()] = input_peer self.peers_by_id[peer_id] = input_peer return input_peer From 9e386ed24c54eb0b017d5b13a4282592b92be530 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 19 Mar 2018 01:51:47 +0100 Subject: [PATCH 16/19] Remove resolve_username method --- pyrogram/client/client.py | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index e3968b31..57706657 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -865,35 +865,6 @@ class Client: offset_date = parse_dialogs(dialogs) log.info("Entities count: {}".format(len(self.peers_by_id))) - def resolve_username(self, username: str): - username = username.lower().strip("@") - - resolved_peer = self.send( - functions.contacts.ResolveUsername( - username=username - ) - ) # type: types.contacts.ResolvedPeer - - if type(resolved_peer.peer) is PeerUser: - input_peer = InputPeerUser( - user_id=resolved_peer.users[0].id, - access_hash=resolved_peer.users[0].access_hash - ) - peer_id = input_peer.user_id - elif type(resolved_peer.peer) is PeerChannel: - input_peer = InputPeerChannel( - channel_id=resolved_peer.chats[0].id, - access_hash=resolved_peer.chats[0].access_hash - ) - peer_id = int("-100" + str(input_peer.channel_id)) - else: - raise PeerIdInvalid - - self.peers_by_username[username.lower()] = input_peer - self.peers_by_id[peer_id] = input_peer - - return input_peer - def resolve_peer(self, peer_id: int or str): """Use this method to get the *InputPeer* of a known *peer_id*. @@ -934,7 +905,8 @@ class Client: try: return self.peers_by_username[peer_id] except KeyError: - return self.resolve_username(peer_id) + self.send(functions.contacts.ResolveUsername(peer_id)) + return self.peers_by_username[peer_id] else: try: return self.peers_by_phone[peer_id] From 0f2dc6c62442eacee841759ccd2413ceef97a795 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 19 Mar 2018 01:53:07 +0100 Subject: [PATCH 17/19] Remove unnecessary method calls --- pyrogram/client/client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 57706657..736092b9 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -639,9 +639,6 @@ class Client: ) ) - self.fetch_peers(diff.users) - self.fetch_peers(diff.chats) - self.update_queue.put(( types.UpdateNewMessage( message=diff.new_messages[0], From 299d6aca5c035878ec9d4212e34d15297cb28ff9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 19 Mar 2018 03:37:43 +0100 Subject: [PATCH 18/19] Raise ConnectionError if client is not started --- pyrogram/client/client.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 736092b9..c708c2de 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -154,7 +154,7 @@ class Client: self.auth_key = None self.user_id = None - self.rnd_id = None + self.rnd_id = MsgId self.peers_by_id = {} self.peers_by_username = {} @@ -167,6 +167,7 @@ class Client: self.session = None + self.is_started = None self.is_idle = None self.updates_queue = Queue() @@ -195,6 +196,7 @@ class Client: ) self.session.start() + self.is_started = True if self.user_id is None: if self.token is None: @@ -210,8 +212,6 @@ class Client: else: self.send(functions.updates.GetState()) - self.rnd_id = MsgId - for i in range(self.UPDATES_WORKERS): Thread(target=self.updates_worker, name="UpdatesWorker#{}".format(i + 1)).start() @@ -227,6 +227,7 @@ class Client: """Use this method to manually stop the Client. Requires no parameters. """ + self.is_started = False self.session.stop() for _ in range(self.UPDATES_WORKERS): @@ -754,12 +755,15 @@ class Client: Raises: :class:`pyrogram.Error` """ - r = self.session.send(data) + if self.is_started: + r = self.session.send(data) - self.fetch_peers(getattr(r, "users", [])) - self.fetch_peers(getattr(r, "chats", [])) + self.fetch_peers(getattr(r, "users", [])) + self.fetch_peers(getattr(r, "chats", [])) - return r + return r + else: + raise ConnectionError("client '{}' is not started".format(self.session_name)) def load_config(self): parser = ConfigParser() From 2deea2e4a6e2eb682cf63cb944b203cfefb5aba8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 19 Mar 2018 03:38:09 +0100 Subject: [PATCH 19/19] Remove unused imports --- pyrogram/client/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index c708c2de..20ac58bc 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -46,7 +46,6 @@ from pyrogram.api.errors import ( VolumeLocNotFound, UserMigrate) from pyrogram.api.types import ( User, Chat, Channel, - PeerUser, PeerChannel, InputPeerEmpty, InputPeerSelf, InputPeerUser, InputPeerChat, InputPeerChannel )