diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index 866a756a..a52f9c3d 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -2,6 +2,7 @@ import abc import asyncio import logging import platform +import time import warnings from datetime import timedelta, datetime @@ -192,6 +193,8 @@ class TelegramBaseClient(abc.ABC): self._last_state = datetime.now() self._state_delay = timedelta(hours=1) self._state = None + self._updates_handle = None + self._last_request = time.time() # Some further state for subclasses self._event_builders = [] @@ -241,6 +244,8 @@ class TelegramBaseClient(abc.ABC): await self._sender.send(self._init_with( functions.help.GetConfigRequest())) + self._updates_handle = self._loop.create_task(self._update_loop()) + if not had_auth: self.session.auth_key = self._sender.state.auth_key self.session.save() @@ -386,4 +391,8 @@ class TelegramBaseClient(abc.ABC): def _handle_update(self, update): raise NotImplementedError + @abc.abstractmethod + def _update_loop(self): + raise NotImplementedError + # endregion diff --git a/telethon/client/updates.py b/telethon/client/updates.py index 4685444e..f8967a29 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -1,6 +1,8 @@ import asyncio import itertools import logging +import random +import time import warnings from .users import UserMethods @@ -170,6 +172,35 @@ class UpdateMethods(UserMethods): update._entities = {} self._loop.create_task(self._dispatch_update(update)) + async def _update_loop(self): + # Pings' ID don't really need to be secure, just "random" + rnd = lambda: random.randrange(-2**63, 2**63) + while self.is_connected(): + try: + await asyncio.wait_for(self.disconnected, timeout=60) + continue # We actually just want to act upon timeout + except asyncio.TimeoutError: + pass + except: + continue # Any disconnected exception should be ignored + + # We also don't really care about their result. + # Just send them periodically. + self._sender.send(functions.PingRequest(rnd())) + + # We need to send some content-related request at least hourly + # for Telegram to keep delivering updates, otherwise they will + # just stop even if we're connected. Do so every 30 minutes. + # + # TODO Call getDifference instead since it's more relevant + if time.time() - self._last_request > 30 * 60: + if not await self.is_user_authorized(): + # What can be the user doing for so + # long without being logged in...? + continue + + await self(functions.updates.GetStateRequest()) + async def _dispatch_update(self, update): if self._events_pending_resolve: if self._event_resolve_lock.locked(): diff --git a/telethon/client/users.py b/telethon/client/users.py index dda09d75..7bc396aa 100644 --- a/telethon/client/users.py +++ b/telethon/client/users.py @@ -1,5 +1,6 @@ import asyncio import itertools +import time from .telegrambaseclient import TelegramBaseClient from .. import errors, utils @@ -16,6 +17,7 @@ class UserMethods(TelegramBaseClient): raise _NOT_A_REQUEST await r.resolve(self, utils) + self._last_request = time.time() for _ in range(retries): try: future = self._sender.send(request, ordered=ordered) diff --git a/telethon/network/mtprotosender.py b/telethon/network/mtprotosender.py index eee4af73..f7db0036 100644 --- a/telethon/network/mtprotosender.py +++ b/telethon/network/mtprotosender.py @@ -384,7 +384,6 @@ class MTProtoSender: __log__.debug('Receiving items from the network...') body = await self._connection.recv() except asyncio.TimeoutError: - # TODO If nothing is received for a minute, send a request continue except Exception as e: if isinstance(e, ConnectionError):