Cache tokens and improve Oqee login flow

Add local token caching and validation for Oqee client, including _is_token_valid, load_cache and save_cache using .oqee_token.json to reuse valid JWTs and avoid unnecessary re-authentication. Update login() to try loading cache first, keep existing credential/IP login flows with clearer logging and improved fallback/error handling, and save tokens after successful auth. Also add required imports (json, os, time) and minor logging tweak in utils/downloader.py to include the Widevine CDM device filename in the fetch message.
This commit is contained in:
√(noham)²
2026-05-26 17:21:54 +02:00
parent c49254a6e4
commit 8f115881e2
2 changed files with 86 additions and 31 deletions

View File

@@ -51,7 +51,8 @@ def get_keys(kids: list[str], method: dict) -> list[str]:
keys.append(f"{kid}:{key}") keys.append(f"{kid}:{key}")
return keys return keys
logger.info("Fetching DRM keys via Widevine CDM...") cdm_name = (method["device_file"]).split("/")[-1]
logger.info(f"Fetching DRM keys via Widevine CDM {cdm_name}...")
client = method["client_class"] client = method["client_class"]
device = Device.load(method["device_file"]) device = Device.load(method["device_file"])

View File

@@ -1,6 +1,9 @@
"""OQEE streaming service client for authentication and content access.""" """OQEE streaming service client for authentication and content access."""
import base64 import base64
import json
import os
import time
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import requests import requests
@@ -92,6 +95,50 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
logger.info("Logging in") logger.info("Logging in")
self.login(username, password) self.login(username, password)
def _is_token_valid(self, token):
"""Check if JWT token is still valid (checking expiry)."""
if not token:
return False
try:
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
payload = json.loads(base64.b64decode(payload_b64))
return payload.get("exp", 0) > time.time()
except Exception:
return False
def load_cache(self):
"""Load tokens from cache file."""
if os.path.exists(".oqee_token.json"):
try:
with open(".oqee_token.json", "r", encoding="utf-8") as f:
cache = json.load(f)
if self._is_token_valid(
cache.get("access_token")
) and self._is_token_valid(cache.get("right_token")):
self.access_token = cache.get("access_token")
self.right_token = cache.get("right_token")
self.profil_id = cache.get("profil_id")
return True
except Exception as e:
logger.debug("Failed to load token cache: %s", e)
return False
def save_cache(self):
"""Save tokens to cache file."""
try:
with open(".oqee_token.json", "w", encoding="utf-8") as f:
json.dump(
{
"access_token": self.access_token,
"right_token": self.right_token,
"profil_id": self.profil_id,
},
f,
)
except Exception as e:
logger.debug("Failed to save token cache: %s", e)
def _build_headers(self, overrides=None, remove=None): def _build_headers(self, overrides=None, remove=None):
"""Clone default headers and apply optional overrides/removals.""" """Clone default headers and apply optional overrides/removals."""
headers = self._headers_template.copy() headers = self._headers_template.copy()
@@ -338,6 +385,9 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
""" """
Log in to the Oqee service and set up necessary tokens and headers. Log in to the Oqee service and set up necessary tokens and headers.
""" """
if self.load_cache():
logger.info("Loaded valid tokens from cache.")
else:
if not username or not password: if not username or not password:
logger.info("No credentials provided, using IP login.") logger.info("No credentials provided, using IP login.")
self.access_token = self.login_ip() self.access_token = self.login_ip()
@@ -353,7 +403,9 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
logger.info( logger.info(
"Logging in with provided credentials (abo account detected, non-FBX)." "Logging in with provided credentials (abo account detected, non-FBX)."
) )
self.access_token = self.login_cred_mobile(username, password) self.access_token = self.login_cred_mobile(
username, password
)
else: else:
logger.info( logger.info(
"Logging in with provided credentials (free account detected)." "Logging in with provided credentials (free account detected)."
@@ -375,6 +427,8 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
self.profil_id = self.profil() self.profil_id = self.profil()
logger.debug("Profile ID obtained: %s", self.profil_id) logger.debug("Profile ID obtained: %s", self.profil_id)
self.save_cache()
self.headers = self._build_headers( self.headers = self._build_headers(
overrides={ overrides={
"x-fbx-rights-token": self.right_token, "x-fbx-rights-token": self.right_token,