Joyn improvements

This commit is contained in:
Nirvana
2026-08-15 04:20:14 +02:00
parent b6d3030148
commit eb0d3d9fce
2 changed files with 58 additions and 4 deletions
+30 -3
View File
@@ -21,6 +21,7 @@ from .constants import (
DEFAULT_MAX_RETRIES,
DEFAULT_PLATFORM,
DEVICE_IDS,
JOYN_AUTH_ENDPOINTS,
JOYN_AUTH_HEADERS_BASE,
JOYN_CLIENT_VERSION,
JOYN_DOMAINS,
@@ -350,7 +351,31 @@ class JoynAuthenticator(BaseOAuth2Authenticator):
logger.debug("Anonymous token cannot be refreshed")
return None
return super()._refresh_oauth_token()
# Joyn uses a dedicated refresh endpoint, distinct from the
# authorization-code redeem-token endpoint (oauth_token_endpoint).
# grant_type is the token_type (e.g. "Bearer"), matching the
# working reference client, not a standard "refresh_token" value.
payload = {
"refresh_token": self._current_token.refresh_token,
"grant_type": self._current_token.token_type,
"client_id": self._device_id, # Must match the client_id used for login/discovery
"client_name": self.platform,
}
response = self.http_manager.post(
JOYN_AUTH_ENDPOINTS["REFRESH"],
operation="auth",
headers=self._get_joyn_auth_headers(),
json_data=payload,
timeout=getattr(self.config, "timeout", 30),
)
self._check_oauth_error_response(response)
token_data = response.json()
refreshed_token = self._create_token_from_response(token_data)
logger.info("Joyn token refresh successful")
return refreshed_token
except Exception as e:
logger.warning(f"Token refresh failed: {e}")
return None
@@ -372,7 +397,7 @@ class JoynAuthenticator(BaseOAuth2Authenticator):
if not self._web_login_url:
raise Exception("Failed to get web-login URL from SSO discovery")
client_id = self._extracted_client_id or self.oauth_client_id
client_id = self._extracted_client_id or self._device_id
cd1 = self._device_id
session = self._create_oauth_session()
@@ -635,8 +660,10 @@ class JoynAuthenticator(BaseOAuth2Authenticator):
try:
logger.info(f"Starting client credentials flow")
# Joyn's anonymous auth expects a client_id and anon_device_id.
# We use the persistent device_id for both to maintain consistency.
payload = {
"client_id": self.oauth_client_id,
"client_id": self._device_id,
"client_name": self.platform,
"anon_device_id": self._device_id
}
@@ -16,6 +16,25 @@ JOYN_LOGO = "https://upload.wikimedia.org/wikipedia/de/thumb/7/74/Joyn_%28Stream
JOYN_7PASS_BASE_URL = "https://auth.7pass.de"
# 7pass OIDC endpoints — reference only. auth.py's login flow hardcodes these same
# paths inline (verified against working traffic) rather than importing this dict;
# keep the two in sync if you change one.
JOYN_7PASS_ENDPOINTS = {
"AUTHORIZE": f"{JOYN_7PASS_BASE_URL}/authz-srv/authz",
"TOKEN": f"{JOYN_7PASS_BASE_URL}/token",
"LOGIN": f"{JOYN_7PASS_BASE_URL}/login-srv/login",
"CONSENT_ACCEPT": f"{JOYN_7PASS_BASE_URL}/consent-management-srv/consent/scope/accept",
"PRECHECK_CONTINUE": f"{JOYN_7PASS_BASE_URL}/login-srv/precheck/continue",
"USER_CHECK_EXISTS": f"{JOYN_7PASS_BASE_URL}/users-srv/user/checkexists",
"REGISTRATION_SETUP": f"{JOYN_7PASS_BASE_URL}/registration-setup-srv/public/list",
"VERIFICATION_CONFIGURED": f"{JOYN_7PASS_BASE_URL}/verification-srv/v2/setup/public/configured/list",
}
# Joyn auth endpoints (non-OIDC)
JOYN_AUTH_ENDPOINTS = {
"REFRESH": "https://auth.joyn.de/auth/refresh", # Token refresh endpoint
}
# OAuth2 Configuration
JOYN_OAUTH_SCOPE = "openid email profile offline_access"
@@ -34,14 +53,16 @@ JOYN_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
JOYN_CLIENT_VERSION = "5.1344.1"
DEFAULT_PLATFORM = "web"
# Base authentication headers (without dynamic values)
JOYN_AUTH_HEADERS_BASE = {
"User-Agent": JOYN_USER_AGENT,
"Accept": "application/json",
"Content-Type": "application/json",
"Origin": "https://www.joyn.de",
"Origin": "https://www.joyn.de", # Base origin, overridden per country
"joyn-client-version": JOYN_CLIENT_VERSION,
}
# Base API headers (without auth token)
JOYN_API_BASE_HEADERS = {
"Accept": "application/json",
"Content-Type": "application/json",
@@ -54,16 +75,19 @@ JOYN_API_BASE_HEADERS = {
JOYN_GRAPHQL_BASE_URL = "https://api.joyn.de/graphql"
# GraphQL persisted query hashes
GRAPHQL_QUERY_HASHES = {
"LIVE_PLAYER": "52b37a3cf5bc75e56026aed7b0d234874eeabd2eccd369d0cd3d3a6ea15ef566",
"LIVE_CHANNELS": "b7703103ddd0516be6b49ed66186092a6c6f6d815ccc502a9f50800a8cc18dd2",
}
# GraphQL endpoints with full URLs
JOYN_GRAPHQL_ENDPOINTS = {
"LIVE_PLAYER": f'{JOYN_GRAPHQL_BASE_URL}?operationName=PageLivePlayerClientSide&enable_user_location=true&watch_assistant_variant=true&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22{GRAPHQL_QUERY_HASHES["LIVE_PLAYER"]}%22%7D%7D',
"LIVE_CHANNELS": f"{JOYN_GRAPHQL_BASE_URL}?operationName=LiveChannelsAndEpg&enable_user_location=true&watch_assistant_variant=true",
}
# Base GraphQL headers
JOYN_GRAPHQL_BASE_HEADERS = {
"X-Api-Key": "4f0fd9f18abbe3cf0e87fdb556bc39c8",
"Accept": "application/json",
@@ -71,6 +95,7 @@ JOYN_GRAPHQL_BASE_HEADERS = {
"User-Agent": JOYN_USER_AGENT,
}
# GraphQL query defaults
GRAPHQL_PERSISTED_QUERY_VERSION = 1
GRAPHQL_LIVE_CHANNELS_FILTER = "DEFAULT"
GRAPHQL_MAX_RESULTS = 5000
@@ -85,6 +110,7 @@ JOYN_STREAMING_ENDPOINTS = {
"PLAYLIST": "https://api.vod-prd.s.joyn.de/v1/channel/{channel_id}/playlist",
}
# Default video configuration for playlist requests
DEFAULT_VIDEO_CONFIG = {
"enableDolbyAtmos": True,
"enableSubtitles": True,
@@ -99,6 +125,7 @@ DEFAULT_VIDEO_CONFIG = {
"maxSecurityLevel": 5,
}
# Signature secret key (base64 encoded)
SIGNATURE_SECRET_KEY = "MzU0MzM3MzgzMzM4MzMzNjM1NDMzNzM4MzYzNDM2MzYzNTQzMzczODM2MzYzMzM4MzIzNjM1NDMzNzM4MzMzMDM2MzQzNTM5MzU0MzM3MzgzMzM5MzMzNTMyMzQzNTQzMzczODM2MzUzMzM5MzU0MzM3MzgzMzM4MzMzMjMzNDYzNTQzMzczODM2MzYzMzMzMzM0NDMzNDIzNTQzMzczODMzMzgzNjM2MzMzNQ=="
# ============================================================================