mirror of
https://github.com/NohamR/OqeeRewind.git
synced 2026-07-11 19:00:12 +00:00
Add Free Mobile SSO login
Add support for Free Mobile SSO authentication by implementing login_cred_mobile which parses the SSO page with BeautifulSoup and exchanges the resulting token for an OQEE access token. Rename login_cred_abo to login_cred_fbx, introduce a self.fbx flag (and mark 8-digit numeric usernames as abo/non-FBX), and update the login selection logic to call the appropriate handler (FBX vs mobile). Also import BeautifulSoup, simplify some request headers, add bs4 to pyproject.toml, and update the lockfile accordingly.
This commit is contained in:
@@ -11,6 +11,7 @@ authors = [
|
||||
|
||||
dependencies = [
|
||||
"aiohttp>=3.13.2",
|
||||
"bs4>=0.0.2",
|
||||
"InquirerPy>=0.3.4",
|
||||
"python-dotenv>=1.2.1",
|
||||
"pywidevine>=1.9.0",
|
||||
|
||||
117
utils/oqee.py
117
utils/oqee.py
@@ -2,6 +2,7 @@
|
||||
|
||||
import base64
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from bs4 import BeautifulSoup
|
||||
import requests
|
||||
|
||||
from dotenv import load_dotenv
|
||||
@@ -31,15 +32,6 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
|
||||
),
|
||||
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
|
||||
"cache-control": "no-cache",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=0, i",
|
||||
"sec-ch-ua": '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"macOS"',
|
||||
"sec-fetch-dest": "document",
|
||||
"sec-fetch-mode": "navigate",
|
||||
"sec-fetch-site": "none",
|
||||
"sec-fetch-user": "?1",
|
||||
"upgrade-insecure-requests": "1",
|
||||
"user-agent": (
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
||||
@@ -61,6 +53,10 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
|
||||
self.lic_url = "https://license.oqee.net/api/v1/live/license/widevine"
|
||||
if "fbx" in username.lower():
|
||||
self.abo = True
|
||||
self.fbx = True
|
||||
elif len(username) == 8 and username.isdigit():
|
||||
self.abo = True
|
||||
self.fbx = False
|
||||
else:
|
||||
self.abo = False
|
||||
|
||||
@@ -131,7 +127,7 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
|
||||
# logger.info("Selecting first profile by default.")
|
||||
return data["result"][0]["id"]
|
||||
|
||||
def login_cred_abo(self, username, password):
|
||||
def login_cred_fbx(self, username, password):
|
||||
"""Authenticate with OQEE service using Free account credentials."""
|
||||
headers = self._build_headers(
|
||||
overrides={
|
||||
@@ -169,20 +165,12 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Origin": "https://subscribe.free.fr",
|
||||
"Referer": "https://subscribe.free.fr/auth/auth.pl?",
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Sec-Fetch-User": "?1",
|
||||
"Sec-GPC": "1",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"user-agent": (
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/131.0.0.0 Safari/537.36"
|
||||
),
|
||||
"sec-ch-ua": '"Brave";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"macOS"',
|
||||
}
|
||||
data = {
|
||||
"login": username,
|
||||
@@ -214,6 +202,75 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
|
||||
).json()
|
||||
return data["result"]["token"]
|
||||
|
||||
def login_cred_mobile(self, username, password):
|
||||
"""Authenticate with OQEE service using Free mobile 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",
|
||||
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
|
||||
"x-oqee-customization": "0",
|
||||
"x-oqee-platform": "web",
|
||||
}
|
||||
|
||||
data = {"provider": "freemobile", "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]
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Origin": "https://mobile.free.fr",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
|
||||
}
|
||||
|
||||
page = self.session.get(redirect_url, headers=headers)
|
||||
soup = BeautifulSoup(page.text, "html.parser")
|
||||
|
||||
token_csrf = soup.find("input", {"name": "token-csrf"})
|
||||
token_csrf_value = token_csrf["value"] if token_csrf else None
|
||||
callback_url = soup.find("input", {"name": "callback_url"})
|
||||
callback_url_value = callback_url["value"] if callback_url else None
|
||||
|
||||
params = (
|
||||
("service", "oqee"),
|
||||
("token", token),
|
||||
)
|
||||
|
||||
data = {
|
||||
"token-csrf": token_csrf_value,
|
||||
"callback_url": callback_url_value,
|
||||
"identifiant": username,
|
||||
"password": password,
|
||||
"action": "sso-submit",
|
||||
}
|
||||
|
||||
response = self.session.post(
|
||||
"https://mobile.free.fr/sso/", headers=headers, params=params, data=data
|
||||
)
|
||||
|
||||
parsed_url = parse_qs(urlparse(response.url).query)
|
||||
if "result" not in parsed_url:
|
||||
raise ValueError(
|
||||
"Login failed: invalid credentials or error in authentication"
|
||||
)
|
||||
freemobile_token = parsed_url["result"][0]
|
||||
|
||||
headers = self._build_headers(
|
||||
overrides={"x-oqee-customization": "0"}, remove=("x-oqee-account-provider",)
|
||||
)
|
||||
data = self.session.post(
|
||||
"https://api.oqee.net/api/v5/user/login",
|
||||
headers=headers,
|
||||
json={"type": "freemobileoa", "token": freemobile_token},
|
||||
).json()
|
||||
return data["result"]["token"]
|
||||
|
||||
def login_cred_free(self, username, password):
|
||||
"""Authenticate with OQEE service using Free account credentials."""
|
||||
headers = {
|
||||
@@ -222,15 +279,6 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
|
||||
"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"
|
||||
@@ -240,7 +288,6 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
|
||||
}
|
||||
|
||||
data = {"provider": "oqee", "platform": "web"}
|
||||
|
||||
response = self.session.post(
|
||||
"https://api.oqee.net/api/v2/user/oauth/init", headers=headers, json=data
|
||||
).json()
|
||||
@@ -297,10 +344,16 @@ class OqeeClient: # pylint: disable=too-many-instance-attributes
|
||||
else:
|
||||
try:
|
||||
if self.abo:
|
||||
logger.info(
|
||||
"Logging in with provided credentials (abo account detected)."
|
||||
)
|
||||
self.access_token = self.login_cred_abo(username, password)
|
||||
if self.fbx:
|
||||
logger.info(
|
||||
"Logging in with provided credentials (abo account detected)."
|
||||
)
|
||||
self.access_token = self.login_cred_fbx(username, password)
|
||||
else:
|
||||
logger.info(
|
||||
"Logging in with provided credentials (abo account detected, non-FBX)."
|
||||
)
|
||||
self.access_token = self.login_cred_mobile(username, password)
|
||||
else:
|
||||
logger.info(
|
||||
"Logging in with provided credentials (free account detected)."
|
||||
|
||||
36
uv.lock
generated
36
uv.lock
generated
@@ -217,6 +217,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.14.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "soupsieve" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bs4"
|
||||
version = "0.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "beautifulsoup4" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698, upload-time = "2024-01-17T18:15:47.371Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189, upload-time = "2024-01-17T18:15:48.613Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.11.12"
|
||||
@@ -758,6 +783,7 @@ version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "bs4" },
|
||||
{ name = "inquirerpy" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "pywidevine" },
|
||||
@@ -775,6 +801,7 @@ dev = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "aiohttp", specifier = ">=3.13.2" },
|
||||
{ name = "bs4", specifier = ">=0.0.2" },
|
||||
{ name = "inquirerpy", specifier = ">=0.3.4" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||
{ name = "pywidevine", specifier = ">=1.9.0" },
|
||||
@@ -1194,6 +1221,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soupsieve"
|
||||
version = "2.8.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabulate"
|
||||
version = "0.9.0"
|
||||
|
||||
Reference in New Issue
Block a user