diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 34db202f..93e71da6 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -264,6 +264,7 @@ def pyrogram_api(): send_recovery_code recover_password accept_terms_of_service + log_out """, advanced=""" Advanced diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 964bf440..62b09d4e 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -38,12 +38,8 @@ 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, PhoneNumberInvalid, - PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, - PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, - PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, - VolumeLocNotFound, UserMigrate, ChannelPrivate, PhoneNumberOccupied, - PasswordRecoveryNa, PasswordEmpty, AuthBytesInvalid, + PhoneMigrate, NetworkMigrate, SessionPasswordNeeded, + FloodWait, PeerIdInvalid, VolumeLocNotFound, UserMigrate, ChannelPrivate, AuthBytesInvalid, BadRequest) from pyrogram.session import Auth, Session from .ext import utils, Syncer, BaseClient, Dispatcher @@ -52,8 +48,6 @@ 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. @@ -68,24 +62,24 @@ class Client(Methods, BaseClient): :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can pass here as argument. - api_id (``int``, *optional*): - The *api_id* part of your Telegram API Key, as integer. E.g.: 12345 + 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 pass it if you don't want to use the *config.ini* file. + 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 X.Y.Z" + 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()* + 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()* + 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*): @@ -99,69 +93,52 @@ class Client(Methods, BaseClient): proxy (``dict``, *optional*): Your SOCKS5 Proxy settings as dict, e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*. - *username* and *password* can be omitted if your proxy doesn't require authorization. + 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. Defaults to False. - Only applicable for new sessions and will be ignored in case previously - created sessions are loaded. + 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`` | ``callable``, *optional*): + phone_number (``str``, *optional*): Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. - Or pass a callback function which accepts no arguments and must return the correct phone number as string - (e.g., "391234567890"). Only applicable for new sessions. - phone_code (``str`` | ``callable``, *optional*): - Pass the phone code as string (for test numbers only) to avoid entering it manually. Or pass a callback - function which accepts a single positional argument *(phone_number)* and must return the correct phone code - as string (e.g., "12345"). + 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. - Or pass a callback function which accepts a single positional argument *(password_hint)* and must return - the correct password as string (e.g., "password"). Only applicable for new sessions. - recovery_code (``callable``, *optional*): - Pass a callback function which accepts a single positional argument *(email_pattern)* and must return the - correct password recovery code as string (e.g., "987654"). - Only applicable for new sessions. - - force_sms (``str``, *optional*): + force_sms (``bool``, *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 as string to avoid entering it manually. Or pass a callback function which accepts no - arguments and must return the correct name as string (e.g., "Dan"). 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: "". Only applicable for new sessions. + Defaults to False. workers (``int``, *optional*): - Number of maximum concurrent workers 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 - where Pyrogram will store your session files. Defaults to the parent directory of the main script. + 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 + 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. + This is an alternative way setup plugins if you don't want to use the *config.ini* file. no_updates (``bool``, *optional*): Pass True to completely disable incoming updates for the current session. @@ -175,17 +152,6 @@ 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). - - Example: - .. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - with app: - app.send_message("me", "Hi!") - """ def __init__( @@ -202,12 +168,9 @@ class Client(Methods, BaseClient): test_mode: bool = False, bot_token: str = None, phone_number: str = None, - phone_code: Union[str, callable] = None, + phone_code: str = None, password: str = None, - recovery_code: callable = None, force_sms: bool = False, - first_name: str = None, - last_name: str = None, workers: int = BaseClient.WORKERS, workdir: str = BaseClient.WORKDIR, config_file: str = BaseClient.CONFIG_FILE, @@ -232,10 +195,7 @@ class Client(Methods, BaseClient): self.phone_number = phone_number self.phone_code = phone_code self.password = password - self.recovery_code = recovery_code self.force_sms = force_sms - self.first_name = first_name - self.last_name = last_name self.workers = workers self.workdir = Path(workdir) self.config_file = Path(config_file) @@ -260,7 +220,10 @@ class Client(Methods, BaseClient): return self.start() def __exit__(self, *args): - self.stop() + try: + self.stop() + except ConnectionError: + pass async def __aenter__(self): return await self.start() @@ -349,20 +312,18 @@ class Client(Methods, BaseClient): asyncio.ensure_future(self.updates_worker()) ) - log.info("Started {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) + 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()) ) - log.info("Started {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) + logging.info("Started {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) await self.dispatcher.start() await Syncer.add(self) - Syncer.add(self) - self.is_initialized = True async def terminate(self): @@ -379,7 +340,7 @@ class Client(Methods, BaseClient): if self.takeout_id: await self.send(functions.account.FinishTakeoutSession()) - log.warning("Takeout session {} finished".format(self.takeout_id)) + logging.warning("Takeout session {} finished".format(self.takeout_id)) await Syncer.remove(self) await self.dispatcher.stop() @@ -392,7 +353,7 @@ class Client(Methods, BaseClient): self.download_worker_tasks.clear() - log.info("Stopped {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) + logging.info("Stopped {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) for _ in range(Client.UPDATES_WORKERS): self.updates_queue.put_nowait(None) @@ -402,7 +363,7 @@ class Client(Methods, BaseClient): self.updates_worker_tasks.clear() - log.info("Stopped {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) + logging.info("Stopped {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) for media_session in self.media_sessions.values(): await media_session.stop() @@ -689,36 +650,37 @@ class Client(Methods, BaseClient): return True async def authorize(self) -> User: - if self.bot_token is not None: + if self.bot_token: return await self.sign_in_bot(self.bot_token) while True: - if self.phone_number is None: - while True: - value = await ainput("Enter phone number or bot token: ") - confirm = await ainput("Is \"{}\" correct? (y/n): ".format(value)) - - if confirm in ("y", "1"): - break - elif confirm in ("n", "2"): - continue - - if ":" in value: - self.bot_token = value - return await self.sign_in_bot(value) - else: - self.phone_number = value - 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 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 @@ -735,7 +697,7 @@ class Client(Methods, BaseClient): )) while True: - if self.phone_code is None: + if not self.phone_code: self.phone_code = await ainput("Enter confirmation code: ") try: @@ -749,14 +711,14 @@ class Client(Methods, BaseClient): while True: print("Password hint: {}".format(await self.get_password_hint())) - if self.password is None: + if not self.password: self.password = await ainput("Enter password (empty to recover): ") try: - if self.password == "": + if not self.password: confirm = await ainput("Confirm password recovery (y/n): ") - if confirm in ("y", "1"): + if confirm == "y": email_pattern = await self.send_recovery_code() print("The recovery code has been sent to {}".format(email_pattern)) @@ -771,10 +733,9 @@ class Client(Methods, BaseClient): print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) except Exception as e: - log.error(e, exc_info=True) + logging.error(e, exc_info=True) raise - - elif confirm in ("n", "2"): + else: self.password = None else: return await self.check_password(self.password) @@ -784,14 +745,9 @@ class Client(Methods, BaseClient): 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 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 @@ -799,20 +755,18 @@ class Client(Methods, BaseClient): return signed_in while True: - self.first_name = await ainput("Enter first name: ") - self.last_name = await ainput("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 = await self.sign_up( self.phone_number, sent_code.phone_code_hash, - self.first_name, - self.last_name + first_name, + last_name ) except BadRequest as e: print(e.MESSAGE) - self.first_name = None - self.last_name = None except FloodWait as e: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) @@ -825,7 +779,28 @@ class Client(Methods, BaseClient): return signed_up - def start(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 destroyed. + 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.destroy() + + 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 @@ -850,25 +825,25 @@ class Client(Methods, BaseClient): 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 - log.warning("Takeout session {} initiated".format(self.takeout_id)) + self.takeout_id = (await self.send(functions.account.InitTakeoutSession())).id + logging.warning("Takeout session {} initiated".format(self.takeout_id)) - self.send(functions.updates.GetState()) - except Exception as e: - self.disconnect() - raise e + await self.send(functions.updates.GetState()) + except (Exception, KeyboardInterrupt): + await self.disconnect() + raise else: - self.initialize() + await self.initialize() return self - def stop(self): + async def stop(self): """Stop the Client. This method disconnects the client from Telegram and stops the underlying tasks. @@ -892,8 +867,8 @@ class Client(Methods, BaseClient): app.stop() """ - self.terminate() - self.disconnect() + await self.terminate() + await self.disconnect() return self @@ -976,8 +951,8 @@ class Client(Methods, BaseClient): app3.stop() """ - def signal_handler(*args): - log.info("Stop signal received ({}). Exiting...".format(args[0])) + def signal_handler(_, __): + logging.info("Stop signal received ({}). Exiting...".format(_)) Client.is_idling = False for s in stop_signals: @@ -1199,254 +1174,6 @@ class Client(Methods, BaseClient): self.parse_mode = parse_mode - async def authorize_bot(self): - try: - r = await self.send( - functions.auth.ImportBotAuthorization( - flags=0, - api_id=self.api_id, - api_hash=self.api_hash, - bot_auth_token=self.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() - await self.authorize_bot() - else: - self.storage.user_id = r.user.id - - print("Logged in successfully as @{}".format(r.user.username)) - - 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_invalid_raises = self.password is not None - first_name_invalid_raises = self.first_name is not None - - async def default_phone_number_callback(): - while True: - phone_number = await ainput("Enter phone number: ") - confirm = await ainput("Is \"{}\" correct? (y/n): ".format(phone_number)) - - if confirm in ("y", "1"): - return phone_number - elif confirm in ("n", "2"): - continue - - while True: - self.phone_number = ( - await default_phone_number_callback() if self.phone_number is None - else str(await self.phone_number()) if callable(self.phone_number) - else str(self.phone_number) - ) - - self.phone_number = self.phone_number.strip("+") - - try: - r = await self.send( - functions.auth.SendCode( - phone_number=self.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() - except (PhoneNumberInvalid, PhoneNumberBanned) as e: - if phone_number_invalid_raises: - raise - else: - print(e.MESSAGE) - self.phone_number = None - except FloodWait as e: - if phone_number_invalid_raises: - raise - else: - print(e.MESSAGE.format(x=e.x)) - await asyncio.sleep(e.x) - except Exception as e: - log.error(e, exc_info=True) - raise - else: - break - - phone_registered = r.phone_registered - phone_code_hash = r.phone_code_hash - terms_of_service = r.terms_of_service - - if terms_of_service and not Client.terms_of_service_displayed: - print("\n" + terms_of_service.text + "\n") - Client.terms_of_service_displayed = True - - if self.force_sms: - await self.send( - functions.auth.ResendCode( - phone_number=self.phone_number, - phone_code_hash=phone_code_hash - ) - ) - - while True: - if not phone_registered: - self.first_name = ( - await ainput("First name: ") if self.first_name is None - else str(await self.first_name()) if callable(self.first_name) - else str(self.first_name) - ) - - self.last_name = ( - await ainput("Last name: ") if self.last_name is None - else str(await self.last_name()) if callable(self.last_name) - else str(self.last_name) - ) - - self.phone_code = ( - await ainput("Enter phone code: ") if self.phone_code is None - else str(await self.phone_code(self.phone_number)) if callable(self.phone_code) - else str(self.phone_code) - ) - - try: - if phone_registered: - try: - r = await self.send( - functions.auth.SignIn( - phone_number=self.phone_number, - phone_code_hash=phone_code_hash, - phone_code=self.phone_code - ) - ) - except PhoneNumberUnoccupied: - log.warning("Phone number unregistered") - phone_registered = False - continue - else: - try: - r = await self.send( - functions.auth.SignUp( - phone_number=self.phone_number, - phone_code_hash=phone_code_hash, - phone_code=self.phone_code, - first_name=self.first_name, - last_name=self.last_name - ) - ) - except PhoneNumberOccupied: - log.warning("Phone number already registered") - phone_registered = True - continue - 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) - - async def default_password_callback(password_hint: str) -> str: - print("Hint: {}".format(password_hint)) - return await ainput("Enter password (empty to recover): ") - - async def default_recovery_callback(email_pattern: str) -> str: - print("An e-mail containing the recovery code has been sent to {}".format(email_pattern)) - return await ainput("Enter password recovery code: ") - - while True: - try: - r = await self.send(functions.account.GetPassword()) - - self.password = ( - await default_password_callback(r.hint) if self.password is None - else str((await self.password(r.hint)) or "") if callable(self.password) - else str(self.password) - ) - - if self.password == "": - r = await self.send(functions.auth.RequestPasswordRecovery()) - - self.recovery_code = ( - await default_recovery_callback(r.email_pattern) if self.recovery_code is None - else str(await self.recovery_code(r.email_pattern)) if callable(self.recovery_code) - else str(self.recovery_code) - ) - - r = await self.send( - functions.auth.RecoverPassword( - code=self.recovery_code - ) - ) - else: - r = await self.send( - functions.auth.CheckPassword( - password=compute_check(r, self.password) - ) - ) - except (PasswordEmpty, PasswordRecoveryNa, PasswordHashInvalid) as e: - if password_invalid_raises: - raise - else: - print(e.MESSAGE) - self.password = None - self.recovery_code = None - except FloodWait as e: - if password_invalid_raises: - raise - else: - print(e.MESSAGE.format(x=e.x)) - await asyncio.sleep(e.x) - self.password = None - self.recovery_code = None - except Exception as e: - log.error(e, exc_info=True) - raise - else: - break - break - except FloodWait as e: - if phone_code_invalid_raises or first_name_invalid_raises: - raise - else: - print(e.MESSAGE.format(x=e.x)) - await asyncio.sleep(e.x) - except Exception as e: - log.error(e, exc_info=True) - raise - else: - break - - if terms_of_service: - assert await self.send( - functions.help.AcceptTermsOfService( - id=terms_of_service.id - ) - ) - - self.password = None - self.storage.user_id = r.user.id - - print("Logged in successfully as {}".format(r.user.first_name)) - def fetch_peers( self, peers: List[ @@ -1546,7 +1273,7 @@ class Client(Methods, BaseClient): os.makedirs(directory, exist_ok=True) shutil.move(temp_file_path, final_file_path) except Exception as e: - log.error(e, exc_info=True) + logging.error(e, exc_info=True) try: os.remove(temp_file_path) @@ -1587,7 +1314,7 @@ class Client(Methods, BaseClient): pts_count = getattr(update, "pts_count", None) if isinstance(update, types.UpdateChannelTooLong): - log.warning(update) + logging.warning(update) if isinstance(update, types.UpdateNewChannelMessage) and is_min: message = update.message @@ -1639,14 +1366,11 @@ class Client(Methods, BaseClient): elif isinstance(updates, types.UpdateShort): self.dispatcher.updates_queue.put_nowait((updates.update, {}, {})) elif isinstance(updates, types.UpdatesTooLong): - log.warning(updates) + logging.info(updates) except Exception as e: - log.error(e, exc_info=True) + logging.error(e, exc_info=True) - 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): """Send raw Telegram queries. This method makes it possible to manually call every single Telegram API method in a low-level manner. @@ -1819,7 +1543,7 @@ class Client(Methods, BaseClient): if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) - log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( + logging.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( self.session_name, type(handler).__name__, name, group, module_path)) count += 1 @@ -1833,12 +1557,12 @@ class Client(Methods, BaseClient): try: module = import_module(module_path) except ImportError: - log.warning('[{}] [LOAD] Ignoring non-existent module "{}"'.format( + logging.warning('[{}] [LOAD] Ignoring non-existent module "{}"'.format( self.session_name, module_path)) continue if "__path__" in dir(module): - log.warning('[{}] [LOAD] Ignoring namespace "{}"'.format( + logging.warning('[{}] [LOAD] Ignoring namespace "{}"'.format( self.session_name, module_path)) continue @@ -1854,13 +1578,13 @@ class Client(Methods, BaseClient): if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) - log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( + logging.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( + logging.warning('[{}] [LOAD] Ignoring non-existent function "{}" from "{}"'.format( self.session_name, name, module_path)) if exclude: @@ -1871,12 +1595,12 @@ class Client(Methods, BaseClient): try: module = import_module(module_path) except ImportError: - log.warning('[{}] [UNLOAD] Ignoring non-existent module "{}"'.format( + logging.warning('[{}] [UNLOAD] Ignoring non-existent module "{}"'.format( self.session_name, module_path)) continue if "__path__" in dir(module): - log.warning('[{}] [UNLOAD] Ignoring namespace "{}"'.format( + logging.warning('[{}] [UNLOAD] Ignoring namespace "{}"'.format( self.session_name, module_path)) continue @@ -1892,20 +1616,20 @@ class Client(Methods, BaseClient): if isinstance(handler, Handler) and isinstance(group, int): self.remove_handler(handler, group) - log.info('[{}] [UNLOAD] {}("{}") from group {} in "{}"'.format( + logging.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( + logging.warning('[{}] [UNLOAD] Ignoring non-existent function "{}" from "{}"'.format( self.session_name, name, module_path)) if count > 0: - log.warning('[{}] Successfully loaded {} plugin{} from "{}"'.format( + logging.warning('[{}] Successfully loaded {} plugin{} from "{}"'.format( self.session_name, count, "s" if count > 1 else "", root)) else: - log.warning('[{}] No plugin loaded from "{}"'.format( + logging.warning('[{}] No plugin loaded from "{}"'.format( self.session_name, root)) # def get_initial_dialogs_chunk(self, offset_date: int = 0): @@ -1922,10 +1646,10 @@ class Client(Methods, BaseClient): # ) # ) # except FloodWait as e: - # log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) + # logging.warning("get_dialogs flood: waiting {} seconds".format(e.x)) # time.sleep(e.x) # else: - # log.info("Total peers: {}".format(self.storage.peers_count)) + # logging.info("Total peers: {}".format(self.storage.peers_count)) # return r # # def get_initial_dialogs(self): @@ -1940,8 +1664,7 @@ class Client(Methods, BaseClient): # # self.get_initial_dialogs_chunk() - async 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. @@ -1980,9 +1703,11 @@ class Client(Methods, BaseClient): try: return self.storage.get_peer_by_username(peer_id) except KeyError: - await self.send(functions.contacts.ResolveUsername(username=peer_id - ) - ) + await self.send( + functions.contacts.ResolveUsername( + username=peer_id + ) + ) return self.storage.get_peer_by_username(peer_id) else: @@ -2094,7 +1819,7 @@ class Client(Methods, BaseClient): try: await asyncio.ensure_future(session.send(data)) except Exception as e: - log.error(e) + logging.error(e) part_size = 512 * 1024 file_size = os.path.getsize(path) @@ -2160,7 +1885,7 @@ class Client(Methods, BaseClient): except Client.StopTransmission: raise except Exception as e: - log.error(e, exc_info=True) + logging.error(e, exc_info=True) else: if is_big: return types.InputFileBig( @@ -2392,7 +2117,7 @@ class Client(Methods, BaseClient): raise e except Exception as e: if not isinstance(e, Client.StopTransmission): - log.error(e, exc_info=True) + logging.error(e, exc_info=True) try: os.remove(file_name) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 15906adf..0638e445 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -50,7 +50,7 @@ class BaseClient: INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$") DIALOGS_AT_ONCE = 100 - UPDATES_WORKERS = 1 + UPDATES_WORKERS = 4 DOWNLOAD_WORKERS = 4 OFFLINE_SLEEP = 900 WORKERS = 4 diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index a2fa472b..cd05f3f9 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -34,8 +34,6 @@ from ..handlers import ( UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler ) -log = logging.getLogger(__name__) - class Dispatcher: NEW_MESSAGE_UPDATES = ( @@ -111,7 +109,7 @@ class Dispatcher: asyncio.ensure_future(self.update_worker(self.locks_list[-1])) ) - log.info("Started {} UpdateWorkerTasks".format(self.workers)) + logging.info("Started {} UpdateWorkerTasks".format(self.workers)) async def stop(self): for i in range(self.workers): @@ -123,7 +121,7 @@ class Dispatcher: self.update_worker_tasks.clear() self.groups.clear() - log.info("Stopped {} UpdateWorkerTasks".format(self.workers)) + logging.info("Stopped {} UpdateWorkerTasks".format(self.workers)) def add_handler(self, handler, group: int): async def fn(): @@ -185,7 +183,7 @@ class Dispatcher: if handler.check(parsed_update): args = (parsed_update,) except Exception as e: - log.error(e, exc_info=True) + logging.error(e, exc_info=True) continue elif isinstance(handler, RawUpdateHandler): @@ -201,10 +199,10 @@ class Dispatcher: except pyrogram.ContinuePropagation: continue except Exception as e: - log.error(e, exc_info=True) + logging.error(e, exc_info=True) break except pyrogram.StopPropagation: pass except Exception as e: - log.error(e, exc_info=True) + logging.error(e, exc_info=True) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index 8b48e6e2..a00d2c70 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -20,8 +20,6 @@ import asyncio import logging import time -log = logging.getLogger(__name__) - class Syncer: INTERVAL = 20 @@ -83,9 +81,9 @@ class Syncer: start = time.time() client.storage.save() except Exception as e: - log.critical(e, exc_info=True) + logging.critical(e, exc_info=True) else: - log.info('Synced "{}" in {:.6} ms'.format( + logging.info('Synced "{}" in {:.6} ms'.format( client.storage.name, (time.time() - start) * 1000 )) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index ad7d2218..d2c31c52 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -26,8 +26,6 @@ from pyrogram.errors import FloodWait from ...ext import BaseClient -log = logging.getLogger(__name__) - class Filters: ALL = "all" @@ -154,7 +152,7 @@ class GetChatMembers(BaseClient): 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)) + logging.warning("Sleeping for {}s".format(e.x)) await asyncio.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 ec25249d..af703615 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -26,8 +26,6 @@ from pyrogram.errors import FloodWait from ...ext import BaseClient, utils -log = logging.getLogger(__name__) - class GetDialogs(BaseClient): async def get_dialogs( @@ -83,7 +81,7 @@ class GetDialogs(BaseClient): ) ) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + logging.warning("Sleeping for {}s".format(e.x)) await asyncio.sleep(e.x) else: break @@ -112,6 +110,6 @@ class GetDialogs(BaseClient): if not isinstance(dialog, types.Dialog): continue - parsed_dialogs.append(pyrogram.Dialog._parse(self, dialog, messages, users, chats)) + parsed_dialogs.append(pyrogram.Dialogging._parse(self, dialog, messages, users, chats)) return pyrogram.List(parsed_dialogs) diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py index 10fd9950..d1ccd728 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -25,8 +25,6 @@ from pyrogram.api import functions from pyrogram.errors import FloodWait from ...ext import BaseClient -log = logging.getLogger(__name__) - class GetContacts(BaseClient): async def get_contacts(self) -> List["pyrogram.User"]: @@ -45,7 +43,7 @@ class GetContacts(BaseClient): try: contacts = await self.send(functions.contacts.GetContacts(hash=0)) except FloodWait as e: - log.warning("get_contacts flood: waiting {} seconds".format(e.x)) + logging.warning("get_contacts flood: waiting {} seconds".format(e.x)) await asyncio.sleep(e.x) else: return pyrogram.List(pyrogram.User._parse(self, user) for user in contacts.users) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 50bc4b8f..c4eec5ce 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -27,8 +27,6 @@ from pyrogram.errors import FloodWait from ...ext import BaseClient -log = logging.getLogger(__name__) - class GetHistory(BaseClient): async def get_history( @@ -104,7 +102,7 @@ class GetHistory(BaseClient): ) ) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + logging.warning("Sleeping for {}s".format(e.x)) await asyncio.sleep(e.x) else: break diff --git a/pyrogram/client/methods/messages/get_history_count.py b/pyrogram/client/methods/messages/get_history_count.py index d235e79b..c496a82b 100644 --- a/pyrogram/client/methods/messages/get_history_count.py +++ b/pyrogram/client/methods/messages/get_history_count.py @@ -16,14 +16,11 @@ # 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.api import types, functions from pyrogram.client.ext import BaseClient -log = logging.getLogger(__name__) - class GetHistoryCount(BaseClient): async def get_history_count( diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index c6c89af6..834e60c5 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -26,8 +26,6 @@ from pyrogram.errors import FloodWait from ...ext import BaseClient, utils -log = logging.getLogger(__name__) - # TODO: Rewrite using a flag for replied messages and have message_ids non-optional @@ -117,7 +115,7 @@ class GetMessages(BaseClient): try: r = await self.send(rpc) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + logging.warning("Sleeping for {}s".format(e.x)) await asyncio.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 1e16d0d3..8df58db9 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -26,8 +26,6 @@ from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient, utils from pyrogram.errors import FloodWait -log = logging.getLogger(__name__) - class SendMediaGroup(BaseClient): # TODO: Add progress parameter @@ -89,7 +87,7 @@ class SendMediaGroup(BaseClient): ) ) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + logging.warning("Sleeping for {}s".format(e.x)) await asyncio.sleep(e.x) else: break @@ -144,7 +142,7 @@ class SendMediaGroup(BaseClient): ) ) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + logging.warning("Sleeping for {}s".format(e.x)) await asyncio.sleep(e.x) else: break @@ -195,7 +193,7 @@ class SendMediaGroup(BaseClient): ) ) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + logging.warning("Sleeping for {}s".format(e.x)) await asyncio.sleep(e.x) else: break diff --git a/pyrogram/client/parser/html.py b/pyrogram/client/parser/html.py index d21f024f..7e5a37f0 100644 --- a/pyrogram/client/parser/html.py +++ b/pyrogram/client/parser/html.py @@ -28,8 +28,6 @@ from pyrogram.api import types from pyrogram.errors import PeerIdInvalid from . import utils -log = logging.getLogger(__name__) - class Parser(HTMLParser): MENTION_RE = re.compile(r"tg://user\?id=(\d+)") @@ -97,7 +95,7 @@ class Parser(HTMLParser): line, offset = self.getpos() offset += 1 - log.warning("Unmatched closing tag at line {}:{}".format(tag, line, offset)) + logging.warning("Unmatched closing tag at line {}:{}".format(tag, line, offset)) else: if not self.tag_entities[tag]: self.tag_entities.pop(tag) @@ -123,7 +121,7 @@ class HTML: for tag, entities in parser.tag_entities.items(): unclosed_tags.append("<{}> (x{})".format(tag, len(entities))) - log.warning("Unclosed tags: {}".format(", ".join(unclosed_tags))) + logging.warning("Unclosed tags: {}".format(", ".join(unclosed_tags))) entities = [] diff --git a/pyrogram/client/storage/file_storage.py b/pyrogram/client/storage/file_storage.py index e6ba8420..a24a46db 100644 --- a/pyrogram/client/storage/file_storage.py +++ b/pyrogram/client/storage/file_storage.py @@ -19,14 +19,13 @@ import base64 import json import logging +import os import sqlite3 from pathlib import Path from threading import Lock from .memory_storage import MemoryStorage -log = logging.getLogger(__name__) - class FileStorage(MemoryStorage): FILE_EXTENSION = ".session" @@ -82,20 +81,20 @@ class FileStorage(MemoryStorage): except ValueError: pass else: - log.warning("JSON session storage detected! Converting it into an SQLite session storage...") + logging.warning("JSON session storage detected! Converting it into an SQLite session storage...") path.rename(path.name + ".OLD") - log.warning('The old session file has been renamed to "{}.OLD"'.format(path.name)) + logging.warning('The old session file has been renamed to "{}.OLD"'.format(path.name)) self.migrate_from_json(session_json) - log.warning("Done! The session has been successfully converted from JSON to SQLite storage") + logging.warning("Done! The session has been successfully converted from JSON to SQLite storage") return if Path(path.name + ".OLD").is_file(): - log.warning('Old session file detected: "{}.OLD". You can remove this file now'.format(path.name)) + logging.warning('Old session file detected: "{}.OLD". You can remove this file now'.format(path.name)) self.conn = sqlite3.connect( str(path), @@ -108,3 +107,6 @@ class FileStorage(MemoryStorage): with self.conn: self.conn.execute("VACUUM") + + def destroy(self): + os.remove(self.database) diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/client/storage/memory_storage.py index e69d247f..ff98fd59 100644 --- a/pyrogram/client/storage/memory_storage.py +++ b/pyrogram/client/storage/memory_storage.py @@ -18,7 +18,6 @@ import base64 import inspect -import logging import sqlite3 import struct import time @@ -29,8 +28,6 @@ from typing import List, Tuple from pyrogram.api import types from pyrogram.client.storage.storage import Storage -log = logging.getLogger(__name__) - class MemoryStorage(Storage): SCHEMA_VERSION = 1 @@ -97,6 +94,9 @@ class MemoryStorage(Storage): with self.lock: self.conn.close() + def destroy(self): + pass + def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): with self.lock: self.conn.executemany( diff --git a/pyrogram/client/storage/storage.py b/pyrogram/client/storage/storage.py index e0810645..cd6438d4 100644 --- a/pyrogram/client/storage/storage.py +++ b/pyrogram/client/storage/storage.py @@ -30,6 +30,9 @@ class Storage: def close(self): raise NotImplementedError + def destroy(self): + raise NotImplementedError + def update_peers(self, peers): raise NotImplementedError diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/client/types/user_and_chats/__init__.py index 29c45e09..fa0fcd46 100644 --- a/pyrogram/client/types/user_and_chats/__init__.py +++ b/pyrogram/client/types/user_and_chats/__init__.py @@ -22,8 +22,9 @@ from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto from .chat_preview import ChatPreview from .dialog import Dialog +from .restriction import Restriction from .user import User __all__ = [ - "Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User" + "Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User", "Restriction" ] diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index b19ea771..914f8d59 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -20,7 +20,6 @@ from struct import pack import pyrogram from pyrogram.api import types -from pyrogram.errors import PeerIdInvalid from ..object import Object from ...ext.utils import encode @@ -60,8 +59,10 @@ class ChatPhoto(Object): loc_big = chat_photo.photo_big try: - peer = client.resolve_peer(peer_id) - except PeerIdInvalid: + # We just want a local storage lookup by id, whose method is not async. + # Otherwise we have to turn this _parse method async and also all the other methods that use this one. + peer = client.storage.get_peer_by_id(peer_id) + except KeyError: return None if isinstance(peer, types.InputPeerUser): diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 2cd55526..ef7ff39b 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -22,8 +22,6 @@ import logging from .transport import * from ..session.internals import DataCenter -log = logging.getLogger(__name__) - class Connection: MAX_RETRIES = 3 @@ -51,14 +49,14 @@ class Connection: self.protocol = self.mode(self.ipv6, self.proxy) try: - log.info("Connecting...") + logging.info("Connecting...") await self.protocol.connect(self.address) except OSError as e: - log.warning(e) # TODO: Remove + logging.warning(e) # TODO: Remove self.protocol.close() await asyncio.sleep(1) else: - log.info("Connected! {} DC{} - IPv{} - {}".format( + logging.info("Connected! {} DC{} - IPv{} - {}".format( "Test" if self.test_mode else "Production", self.dc_id, "6" if self.ipv6 else "4", @@ -66,12 +64,12 @@ class Connection: )) break else: - log.warning("Connection failed! Trying again...") + logging.warning("Connection failed! Trying again...") raise TimeoutError def close(self): self.protocol.close() - log.info("Disconnected") + logging.info("Disconnected") async def send(self, data: bytes): try: diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 0d33fcd9..f76cb250 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -31,8 +31,6 @@ except ImportError as e: raise e -log = logging.getLogger(__name__) - class TCP: TIMEOUT = 10 @@ -67,7 +65,7 @@ class TCP: password=proxy.get("password", None) ) - log.info("Using proxy {}:{}".format(hostname, port)) + logging.info("Using proxy {}:{}".format(hostname, port)) else: self.socket = socks.socksocket( socket.AF_INET6 if ipv6 diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index a155a9a2..9d460708 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged.py @@ -16,12 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import logging - from .tcp import TCP -log = logging.getLogger(__name__) - class TCPAbridged(TCP): def __init__(self, ipv6: bool, proxy: dict): diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index 3ec6a002..cc5de744 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -16,14 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import logging import os from .tcp import TCP from ....crypto.aes import AES -log = logging.getLogger(__name__) - class TCPAbridgedO(TCP): RESERVED = (b"HEAD", b"POST", b"GET ", b"OPTI", b"\xee" * 4) diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index 9404a4aa..119adba0 100644 --- a/pyrogram/connection/transport/tcp/tcp_full.py +++ b/pyrogram/connection/transport/tcp/tcp_full.py @@ -16,14 +16,11 @@ # 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 from struct import pack, unpack from .tcp import TCP -log = logging.getLogger(__name__) - class TCPFull(TCP): def __init__(self, ipv6: bool, proxy: dict): diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py index b2ab88a5..9d833a79 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py @@ -16,13 +16,10 @@ # 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 from .tcp import TCP -log = logging.getLogger(__name__) - class TCPIntermediate(TCP): def __init__(self, ipv6: bool, proxy: dict): diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index 8d7113a5..234b2910 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.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 logging import os from struct import pack, unpack from .tcp import TCP from ....crypto.aes import AES -log = logging.getLogger(__name__) - class TCPIntermediateO(TCP): RESERVED = (b"HEAD", b"POST", b"GET ", b"OPTI", b"\xee" * 4) diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py index d603caa0..9088d57f 100644 --- a/pyrogram/crypto/aes.py +++ b/pyrogram/crypto/aes.py @@ -18,12 +18,10 @@ import logging -log = logging.getLogger(__name__) - try: import tgcrypto - log.info("Using TgCrypto") + logging.info("Using TgCrypto") class AES: @@ -53,7 +51,7 @@ try: except ImportError: import pyaes - log.warning( + logging.warning( "TgCrypto is missing! " "Pyrogram will work the same, but at a much slower speed. " "More info: https://docs.pyrogram.org/topics/tgcrypto" diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index ba0f5aed..d153716e 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -30,8 +30,6 @@ from pyrogram.connection import Connection from pyrogram.crypto import AES, RSA, Prime from .internals import MsgId -log = logging.getLogger(__name__) - class Auth: MAX_RETRIES = 5 @@ -78,34 +76,34 @@ class Auth: 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)) + logging.info("Start creating a new auth key on DC{}".format(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)) + logging.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)) + logging.debug("Got ResPq: {}".format(res_pq.server_nonce)) + logging.debug("Server public key fingerprints: {}".format(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)) + logging.debug("Using fingerprint: {}".format(i)) public_key_fingerprint = i break else: - log.debug("Fingerprint unknown: {}".format(i)) + logging.debug("Fingerprint unknown: {}".format(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)) + logging.debug("Start PQ factorization: {}".format(pq)) start = time.time() 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)) + logging.debug("Done PQ factorization ({}s): {} {}".format(round(time.time() - start, 3), p, q)) # Step 4 server_nonce = res_pq.server_nonce @@ -125,10 +123,10 @@ class Auth: data_with_hash = sha + data + padding encrypted_data = RSA.encrypt(data_with_hash, public_key_fingerprint) - log.debug("Done encrypt data with RSA") + logging.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") + logging.debug("Send req_DH_params") server_dh_params = await self.send( functions.ReqDHParams( nonce=nonce, @@ -162,12 +160,12 @@ class Auth: server_dh_inner_data = TLObject.read(BytesIO(answer)) - log.debug("Done decrypting answer") + logging.debug("Done decrypting answer") 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))) + logging.debug("Delta time: {}".format(round(delta_time, 3))) # Step 6 g = server_dh_inner_data.g @@ -188,7 +186,7 @@ class Auth: data_with_hash = sha + data + padding encrypted_data = AES.ige256_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv) - log.debug("Send set_client_DH_params") + logging.debug("Send set_client_DH_params") set_client_dh_params_answer = await self.send( functions.SetClientDHParams( nonce=nonce, @@ -211,7 +209,7 @@ class Auth: ####################### assert dh_prime == Prime.CURRENT_DH_PRIME - log.debug("DH parameters check: OK") + logging.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") @@ -220,12 +218,12 @@ class Auth: 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) - log.debug("g_a and g_b validation: OK") + logging.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() - log.debug("SHA1 hash values check: OK") + logging.debug("SHA1 hash values check: OK") # https://core.telegram.org/mtproto/security_guidelines#checking-nonce-server-nonce-and-new-nonce-fields # 1st message @@ -238,14 +236,14 @@ class Auth: assert nonce == set_client_dh_params_answer.nonce assert 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") + logging.debug("Nonce fields check: OK") # Step 9 server_salt = AES.xor(new_nonce[:8], server_nonce[:8]) - log.debug("Server salt: {}".format(int.from_bytes(server_salt, "little"))) + logging.debug("Server salt: {}".format(int.from_bytes(server_salt, "little"))) - log.info( + logging.info( "Done auth key exchange: {}".format( set_client_dh_params_answer.__class__.__name__ ) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index c19aeaf3..b5f69081 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -32,8 +32,6 @@ from pyrogram.crypto import MTProto from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated from .internals import MsgId, MsgFactory -log = logging.getLogger(__name__) - class Result: def __init__(self): @@ -158,9 +156,9 @@ class Session: 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())) + logging.info("Session initialized: Layer {}".format(layer)) + logging.info("Device: {} - {}".format(self.client.device_model, self.client.app_version)) + logging.info("System: {} ({})".format(self.client.system_version, self.client.lang_code.upper())) except AuthKeyDuplicated as e: await self.stop() @@ -175,7 +173,7 @@ class Session: self.is_connected.set() - log.info("Session started") + logging.info("Session started") async def stop(self): self.is_connected.clear() @@ -207,16 +205,16 @@ class Session: try: await self.client.disconnect_handler(self.client) except Exception as e: - log.error(e, exc_info=True) + logging.error(e, exc_info=True) - log.info("Session stopped") + logging.info("Session stopped") async def restart(self): await self.stop() await self.start() async def net_worker(self): - log.info("NetWorkerTask started") + logging.info("NetWorkerTask started") while True: packet = await self.recv_queue.get() @@ -238,7 +236,7 @@ class Session: else [data] ) - log.debug(data) + logging.debug(data) for msg in messages: if msg.seq_no % 2 != 0: @@ -271,7 +269,7 @@ class Session: self.results[msg_id].event.set() if len(self.pending_acks) >= self.ACKS_THRESHOLD: - log.info("Send {} acks".format(len(self.pending_acks))) + logging.info("Send {} acks".format(len(self.pending_acks))) try: await self._send(types.MsgsAck(msg_ids=list(self.pending_acks)), False) @@ -280,12 +278,12 @@ class Session: else: self.pending_acks.clear() except Exception as e: - log.error(e, exc_info=True) + logging.error(e, exc_info=True) - log.info("NetWorkerTask stopped") + logging.info("NetWorkerTask stopped") async def ping(self): - log.info("PingTask started") + logging.info("PingTask started") while True: try: @@ -304,10 +302,10 @@ class Session: except (OSError, TimeoutError, RPCError): pass - log.info("PingTask stopped") + logging.info("PingTask stopped") async def next_salt(self): - log.info("NextSaltTask started") + logging.info("NextSaltTask started") while True: now = datetime.now() @@ -317,7 +315,7 @@ class Session: 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( + logging.info("Next salt in {:.0f}m {:.0f}s ({})".format( dt // 60, dt % 60, now + timedelta(seconds=dt) )) @@ -335,10 +333,10 @@ class Session: self.connection.close() break - log.info("NextSaltTask stopped") + logging.info("NextSaltTask stopped") async def recv(self): - log.info("RecvTask started") + logging.info("RecvTask started") while True: packet = await self.connection.recv() @@ -347,7 +345,7 @@ class Session: self.recv_queue.put_nowait(None) if packet: - log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) + logging.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) if self.is_connected.is_set(): asyncio.ensure_future(self.restart()) @@ -356,7 +354,7 @@ class Session: self.recv_queue.put_nowait(packet) - log.info("RecvTask stopped") + logging.info("RecvTask stopped") async def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT): message = self.msg_factory(data)