mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-27 11:32:24 +02:00
Add discovery
This commit is contained in:
@@ -22,6 +22,10 @@ from .constants import (
|
||||
DEFAULT_ENV,
|
||||
DEFAULT_REALM,
|
||||
DEFAULT_TENANT,
|
||||
DISCOVERY_ARKOSE_DATA_PAYLOAD,
|
||||
DISCOVERY_ARKOSE_DATA_URL,
|
||||
DISCOVERY_ARKOSE_FC_URL,
|
||||
DISCOVERY_ARKOSE_SITEKEY,
|
||||
DISCOVERY_AUTH_ORIGIN,
|
||||
DISCOVERY_AUTH_REFERER,
|
||||
DISCOVERY_BOOTSTRAP_URL,
|
||||
@@ -778,6 +782,105 @@ class DiscoveryAuthenticator(BaseAuthenticator):
|
||||
logger.error(f"Unexpected response status: {response.status_code}")
|
||||
response.raise_for_status()
|
||||
|
||||
def _solve_arkose(self, blob: str) -> str:
|
||||
"""
|
||||
Exchange the Arkose blob for a solved FunCaptcha token using
|
||||
Discovery+'s own hosted Arkose endpoint.
|
||||
|
||||
Flow:
|
||||
POST https://a4gds3vfh.discoveryplus.com/fc/gt2/public_key/<sitekey>
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Body (form-encoded):
|
||||
public_key=<sitekey>
|
||||
...
|
||||
data[blob]=<blob from /users/arkose/data>
|
||||
|
||||
The x-ark-esync-value header is the current Unix timestamp rounded
|
||||
down to the nearest hour, which is the sync value Arkose expects.
|
||||
|
||||
Args:
|
||||
blob: Arkose data blob from /users/arkose/data
|
||||
|
||||
Returns:
|
||||
Solved Arkose token string
|
||||
|
||||
Raises:
|
||||
Exception: If the token exchange fails
|
||||
"""
|
||||
import math
|
||||
|
||||
# esync value = current time rounded down to the nearest hour
|
||||
esync_value = str(int(math.floor(time.time() / 3600) * 3600))
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"Origin": DISCOVERY_AUTH_ORIGIN,
|
||||
"Referer": DISCOVERY_AUTH_REFERER,
|
||||
"User-Agent": DISCOVERY_USER_AGENT,
|
||||
"x-ark-esync-value": esync_value,
|
||||
}
|
||||
|
||||
# Full form payload mirrors what the browser sends
|
||||
payload = {
|
||||
"public_key": DISCOVERY_ARKOSE_SITEKEY,
|
||||
"capi_version": "3.7.8",
|
||||
"capi_mode": "lightbox",
|
||||
"style_theme": "dplus",
|
||||
"site": "null",
|
||||
"userbrowser": DISCOVERY_USER_AGENT,
|
||||
"language": "de-DE",
|
||||
"data[blob]": blob,
|
||||
}
|
||||
|
||||
logger.debug("Exchanging Arkose blob for FC token")
|
||||
response = self.http_manager.post(
|
||||
DISCOVERY_ARKOSE_FC_URL,
|
||||
operation="auth",
|
||||
data=payload,
|
||||
headers=headers,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
token = data.get("token")
|
||||
if not token:
|
||||
raise Exception(f"No token in Arkose FC response: {data}")
|
||||
|
||||
logger.debug("Arkose FC token obtained successfully")
|
||||
return token
|
||||
|
||||
def _fetch_arkose_blob(self) -> str:
|
||||
"""
|
||||
Fetch the Arkose data blob required before POST /login.
|
||||
|
||||
Discovery+ requires a solved Arkose (FunCaptcha) token on every /login
|
||||
request. The blob returned here is passed to your Arkose solver which
|
||||
returns the final token to include in x-disco-arkose-token.
|
||||
|
||||
Returns:
|
||||
Arkose blob string
|
||||
|
||||
Raises:
|
||||
Exception: If the blob cannot be fetched
|
||||
"""
|
||||
headers = self._build_base_headers()
|
||||
headers["Authorization"] = f"Bearer {self._current_anon_token}"
|
||||
headers["Content-Type"] = "application/json"
|
||||
headers["Origin"] = DISCOVERY_AUTH_ORIGIN
|
||||
headers["Referer"] = DISCOVERY_AUTH_REFERER
|
||||
|
||||
response = self.http_manager.post(
|
||||
DISCOVERY_ARKOSE_DATA_URL,
|
||||
operation="auth",
|
||||
headers=headers,
|
||||
json_data=DISCOVERY_ARKOSE_DATA_PAYLOAD,
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
blob = data["data"]["attributes"]["blob"]
|
||||
logger.debug(f"Fetched Arkose blob (length={len(blob)})")
|
||||
return blob
|
||||
|
||||
def _perform_user_auth(self) -> BaseAuthToken:
|
||||
"""
|
||||
Perform user authentication by upgrading an anonymous token.
|
||||
@@ -811,26 +914,32 @@ class DiscoveryAuthenticator(BaseAuthenticator):
|
||||
|
||||
# Step 1+2: Obtain anonymous token (also populates session headers
|
||||
# self._disco_id / self._session_state / self._wbd_ace).
|
||||
# NOTE: _perform_anonymous_auth calls _discover_endpoints internally
|
||||
# with the anon token. That's fine — we intentionally overwrite the
|
||||
# endpoint map below with the user token, which may return different
|
||||
# (higher-entitlement) routing.
|
||||
logger.debug("Obtaining anonymous base token for user auth upgrade")
|
||||
anon_token = self._perform_anonymous_auth()
|
||||
|
||||
# Step 3: Upgrade anonymous session to user session
|
||||
# Step 3: Fetch Arkose blob and solve the challenge.
|
||||
# Discovery+ rejects /login with 400 arkose.required without a valid token.
|
||||
logger.debug("Fetching Arkose blob for login challenge")
|
||||
self._current_anon_token = anon_token.access_token
|
||||
arkose_blob = self._fetch_arkose_blob()
|
||||
|
||||
arkose_token = self._solve_arkose(arkose_blob)
|
||||
logger.debug("Arkose challenge solved")
|
||||
|
||||
# Step 4: Upgrade anonymous session to user session
|
||||
logger.debug(
|
||||
f"Upgrading anonymous token to user token for "
|
||||
f"{self.credentials.username}"
|
||||
)
|
||||
|
||||
# Build headers: session headers + Bearer anon token + Origin/Referer
|
||||
# Origin and Referer are required by the /login endpoint (browser sends them).
|
||||
# Build headers: session headers + Bearer anon token + Origin/Referer + Arkose
|
||||
upgrade_headers = self._build_base_headers() # includes session headers
|
||||
upgrade_headers["Authorization"] = f"Bearer {anon_token.access_token}"
|
||||
upgrade_headers["Content-Type"] = "application/json"
|
||||
upgrade_headers["Origin"] = DISCOVERY_AUTH_ORIGIN
|
||||
upgrade_headers["Referer"] = DISCOVERY_AUTH_REFERER
|
||||
upgrade_headers["x-disco-arkose-sitekey"] = DISCOVERY_ARKOSE_SITEKEY
|
||||
upgrade_headers["x-disco-arkose-token"] = arkose_token
|
||||
|
||||
payload = self.credentials.to_login_payload()
|
||||
|
||||
@@ -846,7 +955,6 @@ class DiscoveryAuthenticator(BaseAuthenticator):
|
||||
f"Invalid credentials for user {self.credentials.username}"
|
||||
)
|
||||
|
||||
logger.debug(f"Login 400 response body: {response.text}")
|
||||
response.raise_for_status()
|
||||
token_data = response.json()
|
||||
|
||||
|
||||
@@ -163,6 +163,22 @@ DISCOVERY_DEFAULT_TIMEZONE: Final[str] = "Europe/Berlin"
|
||||
DISCOVERY_AUTH_ORIGIN: Final[str] = "https://auth.discoveryplus.com"
|
||||
DISCOVERY_AUTH_REFERER: Final[str] = "https://auth.discoveryplus.com/"
|
||||
|
||||
# Arkose (FunCaptcha) — required before POST /login
|
||||
DISCOVERY_ARKOSE_SITEKEY: Final[str] = "09CB1E22-0FB7-4AD8-957C-4F06CAFB3C7E"
|
||||
DISCOVERY_ARKOSE_DATA_URL: Final[str] = (
|
||||
"https://default.dplus-emea.prd.api.discoveryplus.com/users/arkose/data"
|
||||
)
|
||||
DISCOVERY_ARKOSE_DATA_PAYLOAD: Final[Dict] = {
|
||||
"data": {
|
||||
"attributes": {"relativePath": "/login", "styleTheme": "dplus"},
|
||||
"type": "arkoseDataExchange",
|
||||
}
|
||||
}
|
||||
# Arkose FunCaptcha token exchange — Discovery+ hosts their own FC endpoint
|
||||
DISCOVERY_ARKOSE_FC_URL: Final[str] = (
|
||||
f"https://a4gds3vfh.discoveryplus.com/fc/gt2/public_key/{DISCOVERY_ARKOSE_SITEKEY}"
|
||||
)
|
||||
|
||||
|
||||
def get_default_device_info() -> Dict[str, any]:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user