From 593f36f18f621002ea26831fc461fd7ac70ce1e8 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sat, 17 Sep 2022 10:32:42 +0200 Subject: [PATCH] Implement a lock to ensure only one verification is done at a time --- twitch.py | 64 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/twitch.py b/twitch.py index dc3e9fd..1ff56de 100644 --- a/twitch.py +++ b/twitch.py @@ -57,42 +57,22 @@ logger = logging.getLogger("TwitchDrops") gql_logger = logging.getLogger("TwitchDrops.gql") -class _IntegrityToken: - def __init__(self, response: JsonType): - self.token: str = response["token"] - self.expires: datetime = datetime.fromtimestamp( - response["expiration"] / 1000, timezone.utc - ) - # verify the integrity token's contents for the "is_bad_bot" flag - stripped_token: str = self.token.split('.')[2] + "==" - messy_json: str = urlsafe_b64decode( - stripped_token.encode() - ).decode(errors="ignore").replace('\n', '') - match = re.search(r'(.+)(?<="}).+$', messy_json) - if match is None: - raise MinerException("Unable to parse the integrity token") - decoded_header: JsonType = json.loads(match.group(1)) - if decoded_header.get("is_bad_bot", "false") != "false": - raise MinerException( - "Twitch considers this miner as a \"Bad Bot\". " - "Try deleting the cookie file and try again." - ) - - @property - def expired(self) -> bool: - return datetime.now(timezone.utc) >= self.expires - - class _AuthState: def __init__(self, twitch: Twitch): self._twitch: Twitch = twitch + self._lock = asyncio.Lock() + self._logged_in = asyncio.Event() self.user_id: int self.device_id: str self.session_id: str self.access_token: str self.client_version: str - self.integrity_token: _IntegrityToken - self._logged_in = asyncio.Event() + self.integrity_token: str + self.integrity_expires: datetime + + @property + def integrity_expired(self) -> bool: + return datetime.now(timezone.utc) >= self.integrity_expires async def _login(self) -> str: logger.debug("Login flow started") @@ -190,10 +170,14 @@ class _AuthState: "X-Device-Id": self.device_id, } if integrity: - headers["Client-Integrity"] = self.integrity_token.token + headers["Client-Integrity"] = self.integrity_token return headers async def verify(self): + async with self._lock: + await self._verify() + + async def _verify(self): if not hasattr(self, "session_id"): self.session_id = create_nonce(CHARS_HEX_LOWER, 16) if not (hasattr(self, "client_version") and hasattr(self, "device_id")): @@ -248,13 +232,31 @@ class _AuthState: # update our cookie and save it jar.update_cookies(cookie, url) jar.save(COOKIES_PATH) - if not hasattr(self, "integrity_token") or self.integrity_token.expired: + if not hasattr(self, "integrity_token") or self.integrity_expired: async with self._twitch.request( "POST", "https://gql.twitch.tv/integrity", headers=self.gql_headers(integrity=False) ) as response: - self.integrity_token = _IntegrityToken(await response.json()) + response_json: JsonType = await response.json() + self.integrity_token = cast(str, response_json["token"]) + self.integrity_expires = datetime.fromtimestamp( + response_json["expiration"] / 1000, timezone.utc + ) + # verify the integrity token's contents for the "is_bad_bot" flag + stripped_token: str = self.integrity_token.split('.')[2] + "==" + messy_json: str = urlsafe_b64decode( + stripped_token.encode() + ).decode(errors="ignore").replace('\n', '') + match = re.search(r'(.+)(?<="}).+$', messy_json) + if match is None: + raise MinerException("Unable to parse the integrity token") + decoded_header: JsonType = json.loads(match.group(1)) + if decoded_header.get("is_bad_bot", "false") != "false": + raise MinerException( + "Twitch considers this miner as a \"Bad Bot\". " + "Try deleting the cookie file and try again." + ) self._logged_in.set()