Compare commits

3 Commits

Author SHA1 Message Date
√(noham)²
05dca3c0cc Clarify auto-detected authentication methods 2026-02-14 14:43:22 +01:00
√(noham)²
8609f6a6a2 Lint 2026-02-14 14:39:41 +01:00
√(noham)²
754632a47a add Oqee free login 2026-02-14 14:37:05 +01:00
4 changed files with 95 additions and 8 deletions

View File

@@ -41,7 +41,12 @@ uv sync
``` ```
### Configuration ### Configuration
Créez un fichier `.env` dans le répertoire racine et ajoutez vos identifiants Oqee (sinon le script essaiera d'abord d'utiliser la connexion par IP) : Le script supporte trois méthodes d'authentification, toutes détectées automatiquement :
1. Connexion par IP : Si vous êtes sur un réseau Free, l'authentification se fait automatiquement via votre adresse IP (aucun identifiant requis)
2. Compte Freebox : Utilisez vos identifiants de compte Free (nom d'utilisateur contenant "fbx")
3. Compte OQEE : Utilisez vos identifiants de compte OQEE standard
Créez un fichier `.env` dans le répertoire racine et ajoutez vos identifiants Oqee (la connexion par IP est utilisée en dernier recours si les identifiants échouent) :
```bash ```bash
OQEE_USERNAME=votre_nom_utilisateur OQEE_USERNAME=votre_nom_utilisateur
OQEE_PASSWORD=votre_mot_de_passe OQEE_PASSWORD=votre_mot_de_passe

View File

@@ -41,7 +41,12 @@ uv sync
``` ```
### Configuration ### Configuration
Create a `.env` file in the root directory and add your Oqee credentials (otherwise the script will try to use IP login first): The script supports three authentication methods, all automatically detected:
1. IP Login: If you're on a Free network, authentication happens automatically via your IP address (no credentials required)
2. Freebox Account: Use your Free account credentials (username containing "fbx")
3. OQEE Account: Use your standard OQEE account credentials
Create a `.env` file in the root directory and add your Oqee credentials (IP login is used as fallback if credentials fail):
```bash ```bash
OQEE_USERNAME=your_username OQEE_USERNAME=your_username
OQEE_PASSWORD=your_password OQEE_PASSWORD=your_password

View File

@@ -65,7 +65,7 @@ def get_date_input():
"type": "input", "type": "input",
"message": "Enter a start date/time (YYYY-MM-DD HH:MM:SS):", "message": "Enter a start date/time (YYYY-MM-DD HH:MM:SS):",
"name": "datetime", "name": "datetime",
"default": "2025-01-01 12:00:00", "default": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"validate": DatetimeValidator(), "validate": DatetimeValidator(),
"invalid_message": "Invalid date/time format. Use YYYY-MM-DD HH:MM:SS", "invalid_message": "Invalid date/time format. Use YYYY-MM-DD HH:MM:SS",
} }
@@ -100,7 +100,7 @@ def get_date_input():
"default": ( "default": (
start_date_result["datetime"] start_date_result["datetime"]
if start_date_result if start_date_result
else "2025-01-01 12:00:00" else datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
), ),
"validate": DatetimeValidator(), "validate": DatetimeValidator(),
"when": lambda answers: answers["input_type"] == "End date/time", "when": lambda answers: answers["input_type"] == "End date/time",

View File

@@ -59,6 +59,10 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
self.right_token = None self.right_token = None
self.profil_id = None self.profil_id = None
self.lic_url = "https://license.oqee.net/api/v1/live/license/widevine" self.lic_url = "https://license.oqee.net/api/v1/live/license/widevine"
if "fbx" in username.lower():
self.abo = True
else:
self.abo = False
self.configure(username, password) self.configure(username, password)
@@ -124,10 +128,10 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
data = self.session.get( data = self.session.get(
"https://api.oqee.net/api/v2/user/profiles", headers=headers "https://api.oqee.net/api/v2/user/profiles", headers=headers
).json() ).json()
logger.info("Selecting first profile by default.") # logger.info("Selecting first profile by default.")
return data["result"][0]["id"] return data["result"][0]["id"]
def login_cred(self, username, password): def login_cred_abo(self, username, password):
"""Authenticate with OQEE service using Free account credentials.""" """Authenticate with OQEE service using Free account credentials."""
headers = self._build_headers( headers = self._build_headers(
overrides={ overrides={
@@ -210,6 +214,66 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
).json() ).json()
return data["result"]["token"] return data["result"]["token"]
def login_cred_free(self, username, password):
"""Authenticate with OQEE service using Free account credentials."""
headers = {
"accept": "*/*",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"cache-control": "no-cache",
"content-type": "application/json",
"origin": "https://tv.free.fr",
"pragma": "no-cache",
"priority": "u=1, i",
"referer": "https://tv.free.fr/",
"sec-ch-ua": '"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"macOS"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site",
"user-agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
),
"x-oqee-customization": "0",
"x-oqee-platform": "web",
}
data = {"provider": "oqee", "platform": "web"}
response = self.session.post(
"https://api.oqee.net/api/v2/user/oauth/init", headers=headers, json=data
).json()
redirect_url = response["result"]["redirect_url"]
token = parse_qs(urlparse(redirect_url).query)["token"][0]
data = {"email": username, "password": password, "token": token}
response = self.session.post(
"https://api.oqee.net/api/v1/user/oauthorize",
headers=headers,
json=data,
).json()
if not response["success"]:
error_msg = response.get("error", {}).get("msg", "No error message")
raise ValueError(
f"Login failed: invalid credentials or error in authentication - {error_msg}"
)
parsed_url = parse_qs(urlparse(response["result"]["redirect_url"]).query)
if "code" not in parsed_url:
raise ValueError(
"Login failed: invalid credentials or error in authentication "
"- no code in redirect URL"
)
code = parsed_url["code"][0]
data = self.session.post(
"https://api.oqee.net/api/v5/user/login",
headers=headers,
json={"type": "oqeeoa", "token": code},
).json()
return data["result"]["token"]
def login_ip(self): def login_ip(self):
""" """
Performs IP-based authentication with the OQEE service. Performs IP-based authentication with the OQEE service.
@@ -231,9 +295,17 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
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()
else: else:
logger.info("Logging in with provided credentials")
try: try:
self.access_token = self.login_cred(username, password) if self.abo:
logger.info(
"Logging in with provided credentials (abo account detected)."
)
self.access_token = self.login_cred_abo(username, password)
else:
logger.info(
"Logging in with provided credentials (free account detected)."
)
self.access_token = self.login_cred_free(username, password)
except ValueError as e: except ValueError as e:
logger.warning( logger.warning(
"Credential login failed: %s. Falling back to IP login.", e "Credential login failed: %s. Falling back to IP login.", e
@@ -242,8 +314,13 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
logger.info("Fetching rights token") logger.info("Fetching rights token")
self.right_token = self.right() self.right_token = self.right()
logger.debug(
"Rights token obtained: %s",
self.right_token[:10] + "..." if self.right_token else "None",
)
logger.info("Fetching profile ID") logger.info("Fetching profile ID")
self.profil_id = self.profil() self.profil_id = self.profil()
logger.debug("Profile ID obtained: %s", self.profil_id)
self.headers = self._build_headers( self.headers = self._build_headers(
overrides={ overrides={