Implement a lock to ensure only one verification is done at a time
This commit is contained in:
1 parent
1b4ae95615
commit
593f36f18f
1 file changed
+33
-31
@@ -57,42 +57,22 @@ logger = logging.getLogger("TwitchDrops")
|
|||||||
gql_logger = logging.getLogger("TwitchDrops.gql")
|
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:
|
class _AuthState:
|
||||||
def __init__(self, twitch: Twitch):
|
def __init__(self, twitch: Twitch):
|
||||||
self._twitch: Twitch = twitch
|
self._twitch: Twitch = twitch
|
||||||
|
self._lock = asyncio.Lock()
|
||||||
|
self._logged_in = asyncio.Event()
|
||||||
self.user_id: int
|
self.user_id: int
|
||||||
self.device_id: str
|
self.device_id: str
|
||||||
self.session_id: str
|
self.session_id: str
|
||||||
self.access_token: str
|
self.access_token: str
|
||||||
self.client_version: str
|
self.client_version: str
|
||||||
self.integrity_token: _IntegrityToken
|
self.integrity_token: str
|
||||||
self._logged_in = asyncio.Event()
|
self.integrity_expires: datetime
|
||||||
|
|
||||||
|
@property
|
||||||
|
def integrity_expired(self) -> bool:
|
||||||
|
return datetime.now(timezone.utc) >= self.integrity_expires
|
||||||
|
|
||||||
async def _login(self) -> str:
|
async def _login(self) -> str:
|
||||||
logger.debug("Login flow started")
|
logger.debug("Login flow started")
|
||||||
@@ -190,10 +170,14 @@ class _AuthState:
|
|||||||
"X-Device-Id": self.device_id,
|
"X-Device-Id": self.device_id,
|
||||||
}
|
}
|
||||||
if integrity:
|
if integrity:
|
||||||
headers["Client-Integrity"] = self.integrity_token.token
|
headers["Client-Integrity"] = self.integrity_token
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
async def verify(self):
|
async def verify(self):
|
||||||
|
async with self._lock:
|
||||||
|
await self._verify()
|
||||||
|
|
||||||
|
async def _verify(self):
|
||||||
if not hasattr(self, "session_id"):
|
if not hasattr(self, "session_id"):
|
||||||
self.session_id = create_nonce(CHARS_HEX_LOWER, 16)
|
self.session_id = create_nonce(CHARS_HEX_LOWER, 16)
|
||||||
if not (hasattr(self, "client_version") and hasattr(self, "device_id")):
|
if not (hasattr(self, "client_version") and hasattr(self, "device_id")):
|
||||||
@@ -248,13 +232,31 @@ class _AuthState:
|
|||||||
# update our cookie and save it
|
# update our cookie and save it
|
||||||
jar.update_cookies(cookie, url)
|
jar.update_cookies(cookie, url)
|
||||||
jar.save(COOKIES_PATH)
|
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(
|
async with self._twitch.request(
|
||||||
"POST",
|
"POST",
|
||||||
"https://gql.twitch.tv/integrity",
|
"https://gql.twitch.tv/integrity",
|
||||||
headers=self.gql_headers(integrity=False)
|
headers=self.gql_headers(integrity=False)
|
||||||
) as response:
|
) 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()
|
self._logged_in.set()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user