diff --git a/MANIFEST.in b/MANIFEST.in
index a1d19d94..f818e13a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,5 @@
## Include
-include COPYING COPYING.lesser NOTICE requirements.txt requirements_extras.txt
+include COPYING COPYING.lesser NOTICE requirements.txt
recursive-include compiler *.py *.tl *.tsv *.txt
## Exclude
diff --git a/README.rst b/README.rst
index b1015db2..e704d46a 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,7 @@
|header|
-Pyrogram |twitter|
-==================
+Pyrogram
+========
.. code-block:: python
@@ -26,8 +26,8 @@ Features
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
-- **Updated** to the latest Telegram API version, currently Layer 76 running on MTProto 2.0.
-- **Documented**: Pyrogram API methods are documented and resemble the Telegram Bot API.
+- **Updated** to the latest Telegram API version, currently Layer 79 on top of MTProto 2.0.
+- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
Requirements
@@ -54,7 +54,7 @@ Getting Started
Contributing
------------
-Pyrogram is brand new! **You are welcome to try it and help make it better** by either submitting pull
+Pyrogram is brand new, and **you are welcome to try it and help make it even better** by either submitting pull
requests or reporting issues/bugs as well as suggesting best practices, ideas, enhancements on both code
and documentation. Any help is appreciated!
@@ -100,28 +100,25 @@ Copyright & License
-
+
-
-.. |twitter| image:: https://media.pyrogram.ml/images/twitter.svg
- :target: https://twitter.com/intent/tweet?text=Build%20custom%20Telegram%20applications%20with%20Pyrogram&url=https://github.com/pyrogram/pyrogram&hashtags=Telegram,MTProto,Python
-
.. |logo| image:: https://pyrogram.ml/images/logo.png
:target: https://pyrogram.ml
:alt: Pyrogram
.. |description| replace:: **Telegram MTProto API Client Library for Python**
-.. |scheme| image:: https://www.pyrogram.ml/images/scheme.svg
+.. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2079-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
:target: compiler/api/source/main_api.tl
- :alt: Scheme Layer 76
+ :alt: Scheme Layer
-.. |tgcrypto| image:: https://www.pyrogram.ml/images/tgcrypto.svg
+.. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
:target: https://github.com/pyrogram/tgcrypto
:alt: TgCrypto
diff --git a/docs/source/resources/AutoAuthorization.rst b/docs/source/resources/AutoAuthorization.rst
index 73504f80..d7a099fe 100644
--- a/docs/source/resources/AutoAuthorization.rst
+++ b/docs/source/resources/AutoAuthorization.rst
@@ -14,27 +14,31 @@ Log In
To automate the **Log In** process, pass your ``phone_number`` and ``password`` (if you have one) in the Client parameters.
If you want to retrieve the phone code programmatically, pass a callback function in the ``phone_code`` field — this
-function must return the correct phone code as string (e.g., "12345") — otherwise, ignore this parameter, Pyrogram will
-ask you to input the phone code manually.
+function accepts a single positional argument (phone_number) and must return the correct phone code (e.g., "12345")
+— otherwise, ignore this parameter, Pyrogram will ask you to input the phone code manually.
+
+Example:
.. code-block:: python
from pyrogram import Client
- def phone_code_callback():
+ def phone_code_callback(phone_number):
code = ... # Get your code programmatically
- return code # Must be string, e.g., "12345"
+ return code # e.g., "12345"
app = Client(
session_name="example",
phone_number="39**********",
- phone_code=phone_code_callback,
+ phone_code=phone_code_callback, # Note the missing parentheses
password="password" # (if you have one)
)
app.start()
+
print(app.get_me())
+
app.stop()
Sign Up
@@ -44,23 +48,27 @@ To automate the **Sign Up** process (i.e., automatically create a new Telegram a
``first_name`` and ``last_name`` fields alongside the other parameters; they will be used to automatically create a new
Telegram account in case the phone number you passed is not registered yet.
+Example:
+
.. code-block:: python
from pyrogram import Client
- def phone_code_callback():
+ def phone_code_callback(phone_number):
code = ... # Get your code programmatically
- return code # Must be string, e.g., "12345"
+ return code # e.g., "12345"
app = Client(
session_name="example",
phone_number="39**********",
- phone_code=phone_code_callback,
+ phone_code=phone_code_callback, # Note the missing parentheses
first_name="Pyrogram",
last_name="" # Can be an empty string
)
app.start()
+
print(app.get_me())
+
app.stop()
\ No newline at end of file
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 298b52c2..e02cc540 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -31,7 +31,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès `
"""
if not self.is_started:
raise ConnectionError("Client has not been started")
- r = await self.session.send(data)
+ r = await self.session.send(data, retries, timeout)
self.fetch_peers(getattr(r, "users", []))
self.fetch_peers(getattr(r, "chats", []))
@@ -847,6 +872,31 @@ class Client(Methods, BaseClient):
"More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
)
+ for option in {"app_version", "device_model", "system_version", "system_lang_code", "lang_code"}:
+ if getattr(self, option):
+ pass
+ else:
+ setattr(self, option, Client.APP_VERSION)
+
+ if parser.has_section("pyrogram"):
+ setattr(self, option, parser.get(
+ "pyrogram",
+ option,
+ fallback=getattr(Client, option.upper())
+ ))
+
+ if self.lang_code:
+ pass
+ else:
+ self.lang_code = Client.LANG_CODE
+
+ if parser.has_section("pyrogram"):
+ self.lang_code = parser.get(
+ "pyrogram",
+ "lang_code",
+ fallback=Client.LANG_CODE
+ )
+
if self._proxy:
self._proxy["enabled"] = True
else:
@@ -1017,7 +1067,7 @@ class Client(Methods, BaseClient):
file_id = file_id or self.rnd_id()
md5_sum = md5() if not is_big and not is_missing_part else None
- session = Session(self.dc_id, self.test_mode, self._proxy, self.auth_key, self.api_id)
+ session = Session(self, self.dc_id, self.auth_key, is_media=True)
await session.start()
try:
@@ -1101,11 +1151,10 @@ class Client(Methods, BaseClient):
)
session = Session(
+ self,
dc_id,
- self.test_mode,
- self._proxy,
await Auth(dc_id, self.test_mode, self._proxy).create(),
- self.api_id
+ is_media=True
)
await session.start()
@@ -1120,11 +1169,10 @@ class Client(Methods, BaseClient):
)
else:
session = Session(
+ self,
dc_id,
- self.test_mode,
- self._proxy,
self.auth_key,
- self.api_id
+ is_media=True
)
await session.start()
@@ -1190,11 +1238,10 @@ class Client(Methods, BaseClient):
if cdn_session is None:
cdn_session = Session(
+ self,
r.dc_id,
- self.test_mode,
- self._proxy,
await Auth(r.dc_id, self.test_mode, self._proxy).create(),
- self.api_id,
+ is_media=True,
is_cdn=True
)
diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py
index a5f4dd91..2a438168 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -17,14 +17,32 @@
# along with Pyrogram. If not, see .
import asyncio
+import platform
import re
+from pyrogram import __version__
from ..style import Markdown, HTML
from ...api.core import Object
+from ...session import Session
from ...session.internals import MsgId
class BaseClient:
+ APP_VERSION = "Pyrogram \U0001f525 {}".format(__version__)
+
+ DEVICE_MODEL = "{} {}".format(
+ platform.python_implementation(),
+ platform.python_version()
+ )
+
+ SYSTEM_VERSION = "{} {}".format(
+ platform.system(),
+ platform.release()
+ )
+
+ SYSTEM_LANG_CODE = "en"
+ LANG_CODE = "en"
+
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$")
BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$")
DIALOGS_AT_ONCE = 100
@@ -76,7 +94,7 @@ class BaseClient:
self.disconnect_handler = None
- def send(self, data: Object):
+ def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT):
pass
def resolve_peer(self, peer_id: int or str):
diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py
index 4ca72592..ad197652 100644
--- a/pyrogram/client/methods/bots/request_callback_answer.py
+++ b/pyrogram/client/methods/bots/request_callback_answer.py
@@ -25,9 +25,8 @@ class RequestCallbackAnswer(BaseClient):
chat_id: int or str,
message_id: int,
callback_data: str):
- """Use this method to request a callback answer from bots. This is the equivalent of clicking an inline button
- containing callback data. The answer contains info useful for clients to display a notification at the top of
- the chat screen or as an alert.
+ """Use this method to request a callback answer from bots. This is the equivalent of clicking an
+ inline button containing callback data.
Args:
chat_id (``int`` | ``str``):
@@ -41,11 +40,21 @@ class RequestCallbackAnswer(BaseClient):
callback_data (``str``):
Callback data associated with the inline button you want to get the answer from.
+
+ Returns:
+ The answer containing info useful for clients to display a notification at the top of the chat screen
+ or as an alert.
+
+ Raises:
+ :class:`Error `
+ ``TimeoutError``: If the bot fails to answer within 10 seconds
"""
return await self.send(
functions.messages.GetBotCallbackAnswer(
peer=self.resolve_peer(chat_id),
msg_id=message_id,
data=callback_data.encode()
- )
+ ),
+ retries=0,
+ timeout=10
)
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index a41ff5fa..9dfe92d1 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -18,7 +18,6 @@
import asyncio
import logging
-import platform
from datetime import datetime, timedelta
from hashlib import sha1
from io import BytesIO
@@ -43,19 +42,8 @@ class Result:
class Session:
- VERSION = __version__
- APP_VERSION = "Pyrogram \U0001f525 {}".format(VERSION)
-
- DEVICE_MODEL = "{} {}".format(
- platform.python_implementation(),
- platform.python_version()
- )
-
- SYSTEM_VERSION = "{} {}".format(
- platform.system(),
- platform.release()
- )
-
+ INITIAL_SALT = 0x616e67656c696361
+ NET_WORKERS = 1
WAIT_TIMEOUT = 15
MAX_RETRIES = 5
ACKS_THRESHOLD = 8
@@ -78,28 +66,24 @@ class Session:
}
def __init__(self,
+ client: pyrogram,
dc_id: int,
- test_mode: bool,
- proxy: dict,
auth_key: bytes,
- api_id: int,
- is_cdn: bool = False,
- client: pyrogram = None):
+ is_media: bool = False,
+ is_cdn: bool = False):
if not Session.notice_displayed:
print("Pyrogram v{}, {}".format(__version__, __copyright__))
print("Licensed under the terms of the " + __license__, end="\n\n")
Session.notice_displayed = True
- self.dc_id = dc_id
- self.test_mode = test_mode
- self.proxy = proxy
- self.api_id = api_id
- self.is_cdn = is_cdn
self.client = client
+ self.dc_id = dc_id
+ self.auth_key = auth_key
+ self.is_media = is_media
+ self.is_cdn = is_cdn
self.connection = None
- self.auth_key = auth_key
self.auth_key_id = sha1(auth_key).digest()[-8:]
self.session_id = Long(MsgId())
@@ -125,7 +109,7 @@ class Session:
async def start(self):
while True:
- self.connection = Connection(DataCenter(self.dc_id, self.test_mode), self.proxy)
+ self.connection = Connection(DataCenter(self.dc_id, self.client.test_mode), self.client.proxy)
try:
await self.connection.connect()
@@ -144,12 +128,14 @@ class Session:
functions.InvokeWithLayer(
layer,
functions.InitConnection(
- self.api_id,
- self.DEVICE_MODEL,
- self.SYSTEM_VERSION,
- self.APP_VERSION,
- "en", "", "en",
- functions.help.GetConfig(),
+ api_id=self.client.api_id,
+ app_version=self.client.app_version,
+ device_model=self.client.device_model,
+ system_version=self.client.system_version,
+ system_lang_code=self.client.system_lang_code,
+ lang_code=self.client.lang_code,
+ lang_pack="",
+ query=functions.help.GetConfig(),
)
)
)
@@ -349,7 +335,7 @@ class Session:
log.info("RecvTask stopped")
- async def _send(self, data: Object, wait_response: bool = True):
+ async def _send(self, data: Object, wait_response: bool = True, timeout: float = WAIT_TIMEOUT):
message = self.msg_factory(data)
msg_id = message.msg_id
@@ -372,7 +358,7 @@ class Session:
if wait_response:
try:
- await asyncio.wait_for(self.results[msg_id].event.wait(), self.WAIT_TIMEOUT)
+ await asyncio.wait_for(self.results[msg_id].event.wait(), timeout)
except asyncio.TimeoutError:
pass
@@ -390,14 +376,14 @@ class Session:
else:
return result
- async def send(self, data: Object, retries: int = MAX_RETRIES):
+ async def send(self, data: Object, retries: int = MAX_RETRIES, timeout: float = WAIT_TIMEOUT):
try:
await asyncio.wait_for(self.is_connected.wait(), self.WAIT_TIMEOUT)
except asyncio.TimeoutError:
pass
try:
- return await self._send(data)
+ return await self._send(data, timeout=timeout)
except (OSError, TimeoutError, InternalServerError) as e:
if retries == 0:
raise e from None
@@ -408,7 +394,7 @@ class Session:
datetime.now(), type(data)))
await asyncio.sleep(0.5)
- return await self.send(data, retries - 1)
+ return await self.send(data, retries - 1, timeout)
# class Result:
# def __init__(self):
@@ -807,4 +793,4 @@ class Session:
# datetime.now(), type(data)))
#
# time.sleep(0.5)
-# return self.send(data, retries - 1)
+# return self.send(data, retries - 1, timeout)