From ea3d119ca61e269f3d5bfdfe42178a82d2dba5e6 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 27 Aug 2019 03:33:44 -0400 Subject: [PATCH] Use X-Ratelimit-Reset-After header by default. There is now an option to turn it off, of course. --- discord/client.py | 11 ++++++++++- discord/http.py | 5 +++-- discord/utils.py | 13 +++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/discord/client.py b/discord/client.py index 43733f3e2..643bc606e 100644 --- a/discord/client.py +++ b/discord/client.py @@ -185,6 +185,14 @@ class Client: In short, this makes it so the only member you can reliably query is the message author. Useful for bots that do not require any state. + assume_unsync_clock: :class:`bool` + Whether to assume the system clock is unsynced. This applies to the ratelimit handling + code. If this is set to ``True``, the default, then the library uses the time to reset + a rate limit bucket given by Discord. If this is ``False`` then your system clock is + used to calculate how long to sleep for. If this is set to ``False`` it is recommended to + sync your system clock to Google's NTP server. + + .. versionadded:: 1.3 Attributes ----------- @@ -203,7 +211,8 @@ class Client: connector = options.pop('connector', None) proxy = options.pop('proxy', None) proxy_auth = options.pop('proxy_auth', None) - self.http = HTTPClient(connector, proxy=proxy, proxy_auth=proxy_auth, loop=self.loop) + unsync_clock = options.pop('assume_unsync_clock', True) + self.http = HTTPClient(connector, proxy=proxy, proxy_auth=proxy_auth, unsync_clock=unsync_clock, loop=self.loop) self._handlers = { 'ready': self._handle_ready diff --git a/discord/http.py b/discord/http.py index 0c4b1a538..70a04bda0 100644 --- a/discord/http.py +++ b/discord/http.py @@ -86,7 +86,7 @@ class HTTPClient: SUCCESS_LOG = '{method} {url} has received {text}' REQUEST_LOG = '{method} {url} with {json} has returned {status}' - def __init__(self, connector=None, *, proxy=None, proxy_auth=None, loop=None): + def __init__(self, connector=None, *, proxy=None, proxy_auth=None, loop=None, unsync_clock=True): self.loop = asyncio.get_event_loop() if loop is None else loop self.connector = connector self.__session = None # filled in static_login @@ -97,6 +97,7 @@ class HTTPClient: self.bot_token = False self.proxy = proxy self.proxy_auth = proxy_auth + self.use_clock = not unsync_clock user_agent = 'DiscordBot (https://github.com/Rapptz/discord.py {0}) Python/{1[0]}.{1[1]} aiohttp/{2}' self.user_agent = user_agent.format(__version__, sys.version_info, aiohttp.__version__) @@ -166,7 +167,7 @@ class HTTPClient: remaining = r.headers.get('X-Ratelimit-Remaining') if remaining == '0' and r.status != 429: # we've depleted our current bucket - delta = utils._parse_ratelimit_header(r) + delta = utils._parse_ratelimit_header(r, use_clock=self.use_clock) log.debug('A rate limit bucket has been exhausted (bucket: %s, retry: %s).', bucket, delta) maybe_lock.defer() self.loop.call_later(delta, lock.release) diff --git a/discord/utils.py b/discord/utils.py index 8090d4a7e..a3162a8a8 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -302,10 +302,15 @@ def _bytes_to_base64_data(data): def to_json(obj): return json.dumps(obj, separators=(',', ':'), ensure_ascii=True) -def _parse_ratelimit_header(request): - now = parsedate_to_datetime(request.headers['Date']) - reset = datetime.datetime.fromtimestamp(float(request.headers['X-Ratelimit-Reset']), datetime.timezone.utc) - return (reset - now).total_seconds() +def _parse_ratelimit_header(request, *, use_clock=False): + reset_after = request.headers.get('X-Ratelimit-Reset-After') + if use_clock or not reset_after: + utc = datetime.timezone.utc + now = datetime.datetime.now(utc) + reset = datetime.datetime.fromtimestamp(float(request.headers['X-Ratelimit-Reset']), utc) + return (reset - now).total_seconds() + else: + return float(reset_after) async def maybe_coroutine(f, *args, **kwargs): value = f(*args, **kwargs)