""" Implements handling ratelimiting on requests. Also adds required headers (auth etc) to the request. Does not actually formulate any requests, only sends them and makes sure to avoid hitting ratelimits Note the contents of this module are to be used for internal purposes, you should never have to interact with the classes and functions in here directly. Implementation details: we avoid hitting ratelimits by keeping counters based on the X-RateLimit-* headers and avoiding hitting them. we avoid getting cloudflare bans by keeping a Counter of errors per minute, and then checking the total amount of errors in the last 10 minutes before sending a request, waiting while it exceeds 9000 (way below the 10000 limit). """ from time import sleep from datetime import datetime, timezone from collections import Counter from requests import Request, Session, Response from . import _values __all__=["RateLimitHandler"] def time(): "Private function. Get time in UTC, no matter our own timezone, using datetime." datetime.now(tz=timezone.utc) return datetime.timestamp() class RateLimitHandler(): """From the module description: Note the contents of this module are to be used for internal purposes, you should never have to interact with [this class] directly. See module description for details. """ def __init__(self): self.global_left=1 self.global_retry_after=0 self.retry_after:dict[str,float] = dict() self.bucket_left:dict[str,float] = dict() self.bucket_keys:dict[str,str] = dict() self.session=Session() # for cloudflare ban avoidance. self.invalid_requests_per_minute = Counter() # for tracking the hard 10000 err/minute limit def __call__(self,request:Request, routekey, _retries=0): "Sends a request to discord, taking ratelimiting into account." self.avoid_cloudflare_ban() # avoid the 1h cloudflare ban on too many errors if not request.url.startswith(_values.BASEURL): request.url=_values.BASEURL+request.url request.headers={ # add required headers for authentication "Authorization":"Bot "+_values.TOKEN, "User-Agent":_values.USERAGENT, "Content-Type":"application/json", "Accept":"application/json" } sendable=self.session.prepare_request(request) if self.global_left <= 0 and time()9000: # stay way below the limit of 10000 minute = self._get_minutes_since_epoch() # remove minutes that were more than 10 minutes ago for key in self.invalid_requests_per_minute.keys(): # should be a short iteration, we should have at max 10-12 items if key