mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-10-30 23:47:33 +03:00 
			
		
		
		
	Change QrLogin to reduce room for error
This commit is contained in:
		
							parent
							
								
									48bd061562
								
							
						
					
					
						commit
						d5cdda28c5
					
				|  | @ -984,3 +984,4 @@ sign_in no longer has phone or phone_hash (these are impl details, and now it's | ||||||
| send code / sign in now only expect a single phone. resend code with new phone is send code, not resend. | send code / sign in now only expect a single phone. resend code with new phone is send code, not resend. | ||||||
| sign_up code is also now a kwarg. and no longer noop if already loggedin. | sign_up code is also now a kwarg. and no longer noop if already loggedin. | ||||||
| start also mandates phone= or password= as kwarg. | start also mandates phone= or password= as kwarg. | ||||||
|  | qrlogin expires has been replaced with timeout and expired for parity with tos and auth. the goal is to hide the error-prone system clock and instead use asyncio's clock. recreate was removed (just call qr_login again; parity with get_tos). class renamed to QrLogin. now must be used in a contextmgr to prevent misuse. | ||||||
|  |  | ||||||
|  | @ -136,7 +136,7 @@ ParticipantPermissions | ||||||
|     :show-inheritance: |     :show-inheritance: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| QRLogin | QrLogin | ||||||
| ======= | ======= | ||||||
| 
 | 
 | ||||||
| .. automodule:: telethon.tl.custom.qrlogin | .. automodule:: telethon.tl.custom.qrlogin | ||||||
|  |  | ||||||
|  | @ -342,10 +342,9 @@ async def send_code_request( | ||||||
|     return _custom.SentCode._new(result) |     return _custom.SentCode._new(result) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> _custom.QRLogin: | def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> _custom.QrLogin: | ||||||
|     qr_login = _custom.QRLogin(self, ignored_ids or []) |     return _custom.QrLoginManager(self, ignored_ids) | ||||||
|     await qr_login.recreate() | 
 | ||||||
|     return qr_login |  | ||||||
| 
 | 
 | ||||||
| async def log_out(self: 'TelegramClient') -> bool: | async def log_out(self: 'TelegramClient') -> bool: | ||||||
|     try: |     try: | ||||||
|  |  | ||||||
|  | @ -531,7 +531,7 @@ class TelegramClient: | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|     @forward_call(auth.qr_login) |     @forward_call(auth.qr_login) | ||||||
|     async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> _custom.QRLogin: |     def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> _custom.QrLogin: | ||||||
|         """ |         """ | ||||||
|         Initiates the QR login procedure. |         Initiates the QR login procedure. | ||||||
| 
 | 
 | ||||||
|  | @ -542,15 +542,18 @@ class TelegramClient: | ||||||
|         whether it's the URL, using the token bytes directly, or generating |         whether it's the URL, using the token bytes directly, or generating | ||||||
|         a QR code and displaying it by other means. |         a QR code and displaying it by other means. | ||||||
| 
 | 
 | ||||||
|         See the documentation for `QRLogin` to see how to proceed after this. |         See the documentation for `QrLogin` to see how to proceed after this. | ||||||
|  | 
 | ||||||
|  |         Note that the login completes once the context manager exits, | ||||||
|  |         not after the ``wait`` method returns. | ||||||
| 
 | 
 | ||||||
|         Arguments |         Arguments | ||||||
|             ignored_ids (List[`int`]): |             ignored_ids (List[`int`]): | ||||||
|                 List of already logged-in user IDs, to prevent logging in |                 List of already logged-in session IDs, to prevent logging in | ||||||
|                 twice with the same user. |                 twice with the same user. | ||||||
| 
 | 
 | ||||||
|         Returns |         Returns | ||||||
|             An instance of `QRLogin`. |             An instance of `QrLogin`. | ||||||
| 
 | 
 | ||||||
|         Example |         Example | ||||||
|             .. code-block:: python |             .. code-block:: python | ||||||
|  | @ -558,11 +561,16 @@ class TelegramClient: | ||||||
|                 def display_url_as_qr(url): |                 def display_url_as_qr(url): | ||||||
|                     pass  # do whatever to show url as a qr to the user |                     pass  # do whatever to show url as a qr to the user | ||||||
| 
 | 
 | ||||||
|                 qr_login = await client.qr_login() |                 async with client.qr_login() as qr_login: | ||||||
|                     display_url_as_qr(qr_login.url) |                     display_url_as_qr(qr_login.url) | ||||||
| 
 | 
 | ||||||
|                     # Important! You need to wait for the login to complete! |                     # Important! You need to wait for the login to complete! | ||||||
|                 await qr_login.wait() |                     # If the context manager exits before the user logs in, the client won't be logged in. | ||||||
|  |                     try: | ||||||
|  |                         user = await qr_login.wait() | ||||||
|  |                         print('Welcome,', user.first_name) | ||||||
|  |                     except asyncio.TimeoutError: | ||||||
|  |                         print('User did not login in time') | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|     @forward_call(auth.log_out) |     @forward_call(auth.log_out) | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ from ._custom import ( | ||||||
|     InlineBuilder, |     InlineBuilder, | ||||||
|     InlineResult, |     InlineResult, | ||||||
|     InlineResults, |     InlineResults, | ||||||
|     QRLogin, |     QrLogin, | ||||||
|     ParticipantPermissions, |     ParticipantPermissions, | ||||||
|     Chat, |     Chat, | ||||||
|     User, |     User, | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ from .button import Button | ||||||
| from .inlinebuilder import InlineBuilder | from .inlinebuilder import InlineBuilder | ||||||
| from .inlineresult import InlineResult | from .inlineresult import InlineResult | ||||||
| from .inlineresults import InlineResults | from .inlineresults import InlineResults | ||||||
| from .qrlogin import QRLogin | from .qrlogin import QrLoginManager, QrLogin | ||||||
| from .participantpermissions import ParticipantPermissions | from .participantpermissions import ParticipantPermissions | ||||||
| from .chat import Chat | from .chat import Chat | ||||||
| from .user import User | from .user import User | ||||||
|  |  | ||||||
|  | @ -1,30 +1,75 @@ | ||||||
| import asyncio | import asyncio | ||||||
| import base64 | import base64 | ||||||
| import datetime | import time | ||||||
|  | import functools | ||||||
| 
 | 
 | ||||||
| from ... import _tl | from ... import _tl | ||||||
| from ..._events.raw import Raw | from ..._events.raw import Raw | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class QRLogin: | class QrLoginManager: | ||||||
|  |     def __init__(self, client, ignored_ids): | ||||||
|  |         self._client = client | ||||||
|  |         self._request = _tl.fn.auth.ExportLoginToken(client._api_id, client._api_hash, ignored_ids or []) | ||||||
|  |         self._event = None | ||||||
|  |         self._handler = None | ||||||
|  |         self._login = None | ||||||
|  | 
 | ||||||
|  |     async def __aenter__(self): | ||||||
|  |         self._event = asyncio.Event() | ||||||
|  |         self._handler = self._client.add_event_handler(self._callback, Raw) | ||||||
|  | 
 | ||||||
|  |         try: | ||||||
|  |             qr = await self._client(self._request) | ||||||
|  |         except: | ||||||
|  |             self._cleanup() | ||||||
|  |             raise | ||||||
|  | 
 | ||||||
|  |         self._login = QrLogin._new(self._client, self._request, qr, self._event) | ||||||
|  |         return self._login | ||||||
|  | 
 | ||||||
|  |     async def __aexit__(self, *args): | ||||||
|  |         try: | ||||||
|  |             # The logic to complete the login is in wait so the user can retrieve the logged-in user | ||||||
|  |             await self._login.wait(timeout=0) | ||||||
|  |             # User logged-in in time | ||||||
|  |         except asyncio.TimeoutError: | ||||||
|  |             pass  # User did not login in time | ||||||
|  |         finally: | ||||||
|  |             self._cleanup() | ||||||
|  | 
 | ||||||
|  |     async def _callback(self, update): | ||||||
|  |         if isinstance(update, _tl.UpdateLoginToken): | ||||||
|  |             self._event.set() | ||||||
|  | 
 | ||||||
|  |     def _cleanup(self): | ||||||
|  |         # Users technically could remove all raw handlers during the procedure but it's unlikely to happen | ||||||
|  |         self._client.remove_event_handler(self._handler) | ||||||
|  |         self._event = None | ||||||
|  |         self._handler = None | ||||||
|  |         self._login = None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class QrLogin: | ||||||
|     """ |     """ | ||||||
|     QR login information. |     QR login information. | ||||||
| 
 | 
 | ||||||
|     Most of the time, you will present the `url` as a QR code to the user, |     Most of the time, you will present the `url` as a QR code to the user, | ||||||
|     and while it's being shown, call `wait`. |     and while it's being shown, call `wait`. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, client, ignored_ids): |     def __init__(self): | ||||||
|         self._client = client |         raise TypeError('You cannot create QrLogin instances by hand!') | ||||||
|         self._request = _tl.fn.auth.ExportLoginToken( |  | ||||||
|             self._client.api_id, self._client.api_hash, ignored_ids) |  | ||||||
|         self._resp = None |  | ||||||
| 
 | 
 | ||||||
|     async def recreate(self): |     @classmethod | ||||||
|         """ |     def _new(cls, client, request, qr, event): | ||||||
|         Generates a new token and URL for a new QR code, useful if the code |         self = cls.__new__(cls) | ||||||
|         has expired before it was imported. |         self._client = client | ||||||
|         """ |         self._request = request | ||||||
|         self._resp = await self._client(self._request) |         self._qr = qr | ||||||
|  |         self._expiry = asyncio.get_running_loop().time() + qr.expires.timestamp() - time.time() | ||||||
|  |         self._event = event | ||||||
|  |         self._user = None | ||||||
|  |         return self | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def token(self) -> bytes: |     def token(self) -> bytes: | ||||||
|  | @ -35,7 +80,7 @@ class QRLogin: | ||||||
|         :tl:`auth.importLoginToken` to log the client that originally |         :tl:`auth.importLoginToken` to log the client that originally | ||||||
|         requested the QR login. |         requested the QR login. | ||||||
|         """ |         """ | ||||||
|         return self._resp.token |         return self._qr.token | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def url(self) -> str: |     def url(self) -> str: | ||||||
|  | @ -54,16 +99,30 @@ class QRLogin: | ||||||
| 
 | 
 | ||||||
|         The URL simply consists of `token` base64-encoded. |         The URL simply consists of `token` base64-encoded. | ||||||
|         """ |         """ | ||||||
|         return 'tg://login?token={}'.format(base64.urlsafe_b64encode(self._resp.token).decode('utf-8').rstrip('=')) |         return 'tg://login?token={}'.format(base64.urlsafe_b64encode(self._qr.token).decode('utf-8').rstrip('=')) | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def expires(self) -> datetime.datetime: |     def timeout(self): | ||||||
|         """ |         """ | ||||||
|         The `datetime` at which the QR code will expire. |         How many seconds are left before `client.qr_login` should be used again. | ||||||
| 
 | 
 | ||||||
|         If you want to try again, you will need to call `recreate`. |         This value is a positive floating point number, and is monotically decreasing. | ||||||
|  |         The value will reach zero after enough seconds have elapsed. This lets you do some work | ||||||
|  |         and call sleep on the value and still wait just long enough. | ||||||
|         """ |         """ | ||||||
|         return self._resp.expires |         return max(0.0, self._expiry - asyncio.get_running_loop().time()) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def expired(self): | ||||||
|  |         """ | ||||||
|  |         Returns `True` if this instance of the QR login has expired and should be re-created. | ||||||
|  | 
 | ||||||
|  |         .. code-block:: python | ||||||
|  | 
 | ||||||
|  |             if qr.expired: | ||||||
|  |                 qr = await client.qr_login() | ||||||
|  |         """ | ||||||
|  |         return asyncio.get_running_loop().time() >= self._expiry | ||||||
| 
 | 
 | ||||||
|     async def wait(self, timeout: float = None): |     async def wait(self, timeout: float = None): | ||||||
|         """ |         """ | ||||||
|  | @ -71,13 +130,12 @@ class QRLogin: | ||||||
|         either by scanning the QR, launching the URL directly, or calling the |         either by scanning the QR, launching the URL directly, or calling the | ||||||
|         import method. |         import method. | ||||||
| 
 | 
 | ||||||
|         This method **must** be called before the QR code is scanned, and |  | ||||||
|         must be executing while the QR code is being scanned. Otherwise, the |  | ||||||
|         login will not complete. |  | ||||||
| 
 |  | ||||||
|         Will raise `asyncio.TimeoutError` if the login doesn't complete on |         Will raise `asyncio.TimeoutError` if the login doesn't complete on | ||||||
|         time. |         time. | ||||||
| 
 | 
 | ||||||
|  |         Note that the login can complete even if `wait` isn't used (if the | ||||||
|  |         context-manager is kept alive for long enough and the users logs in). | ||||||
|  | 
 | ||||||
|         Arguments |         Arguments | ||||||
|             timeout (float): |             timeout (float): | ||||||
|                 The timeout, in seconds, to wait before giving up. By default |                 The timeout, in seconds, to wait before giving up. By default | ||||||
|  | @ -85,26 +143,18 @@ class QRLogin: | ||||||
|                 what you want. |                 what you want. | ||||||
| 
 | 
 | ||||||
|         Returns |         Returns | ||||||
|             On success, an instance of :tl:`User`. On failure it will raise. |             On success, an instance of `User`. On failure it will raise. | ||||||
|         """ |         """ | ||||||
|  |         if self._user: | ||||||
|  |             return self._user | ||||||
|  | 
 | ||||||
|         if timeout is None: |         if timeout is None: | ||||||
|             timeout = (self._resp.expires - datetime.datetime.now(tz=datetime.timezone.utc)).total_seconds() |             timeout = self.timeout | ||||||
| 
 | 
 | ||||||
|         event = asyncio.Event() |  | ||||||
| 
 |  | ||||||
|         async def handler(_update): |  | ||||||
|             event.set() |  | ||||||
| 
 |  | ||||||
|         self._client.add_event_handler(handler, Raw(_tl.UpdateLoginToken)) |  | ||||||
| 
 |  | ||||||
|         try: |  | ||||||
|         # Will raise timeout error if it doesn't complete quick enough, |         # Will raise timeout error if it doesn't complete quick enough, | ||||||
|         # which we want to let propagate |         # which we want to let propagate | ||||||
|             await asyncio.wait_for(event.wait(), timeout=timeout) |         await asyncio.wait_for(self._event.wait(), timeout=timeout) | ||||||
|         finally: |  | ||||||
|             self._client.remove_event_handler(handler) |  | ||||||
| 
 | 
 | ||||||
|         # We got here without it raising timeout error, so we can proceed |  | ||||||
|         resp = await self._client(self._request) |         resp = await self._client(self._request) | ||||||
|         if isinstance(resp, _tl.auth.LoginTokenMigrateTo): |         if isinstance(resp, _tl.auth.LoginTokenMigrateTo): | ||||||
|             await self._client._switch_dc(resp.dc_id) |             await self._client._switch_dc(resp.dc_id) | ||||||
|  | @ -113,7 +163,7 @@ class QRLogin: | ||||||
| 
 | 
 | ||||||
|         if isinstance(resp, _tl.auth.LoginTokenSuccess): |         if isinstance(resp, _tl.auth.LoginTokenSuccess): | ||||||
|             user = resp.authorization.user |             user = resp.authorization.user | ||||||
|             self._client._on_login(user) |             self._user = self._client._update_session_state(user) | ||||||
|             return user |             return self._user | ||||||
| 
 | 
 | ||||||
|         raise TypeError('Login token response was unexpected: {}'.format(resp)) |         raise RuntimeError(f'Unexpected login token response: {resp}') | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user