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