9.4 KiB
9.4 KiB
Analyse des différences entre l'API Rust et Python
Vue d'ensemble
L'implémentation actuelle de pmoqobuz ne suit pas complètement l'API de référence Python (qobuz.api.raw). Voici les principales différences et ce qui doit être corrigé.
Problèmes identifiés
1. ❌ Gestion du secret s4 manquante
Python :
- Accepte soit
appid+configvalue(secret encodé en base64) - Soit utilise le
Spooferpour obtenir l'appID et les secrets dynamiquement - Le
configvalueest décodé et XORé avec l'appID pour obtenir le secrets4 - Le secret
s4est utilisé pour signer certaines requêtes critiques
Rust actuel :
- ❌ Utilise un
DEFAULT_APP_IDcodé en dur - ❌ Pas de gestion du secret
s4 - ❌ Pas d'utilisation du Spoofer pour obtenir l'appID/secret
- ❌ Pas de méthode pour décoder et dériver le secret depuis un
configvalue
Impact :
- Les requêtes
track/getFileUrletuserLibrary/getAlbumsListéchoueront probablement car elles nécessitent une signature MD5
2. ❌ Signature MD5 des requêtes manquante
Python - track_getFileUrl :
ts = str(time.time())
stringvalue = ("trackgetFileUrlformat_id" + fmt_id +
"intent" + intent +
"track_id" + track_id + ts).encode("ASCII")
stringvalue += self.s4 # Secret ajouté
rq_sig = str(hashlib.md5(stringvalue).hexdigest())
params = {
"format_id": fmt_id,
"intent": intent,
"request_ts": ts, # ← Timestamp
"request_sig": rq_sig, # ← Signature MD5
"track_id": track_id,
}
Rust actuel (catalog.rs:210-218) :
let params = [
("track_id", track_id),
("format_id", &format_id),
("intent", "stream"),
// ❌ MANQUE: request_ts
// ❌ MANQUE: request_sig
];
Impact :
- Les requêtes de streaming peuvent échouer ou retourner des URLs invalides
3. ❌ Méthode userlib_getAlbums manquante
Python :
def userlib_getAlbums(self, **ka):
ts = str(time.time())
r_sig = "userLibrarygetAlbumsList" + str(ts) + str(ka["sec"])
r_sig_hashed = hashlib.md5(r_sig.encode("utf-8")).hexdigest()
params = {
"app_id": self.appid,
"user_auth_token": self.user_auth_token,
"request_ts": ts,
"request_sig": r_sig_hashed,
}
return self._api_request(params, "/userLibrary/getAlbumsList")
Rust actuel :
- ❌ Méthode totalement absente
Impact :
- Impossible de tester les secrets (méthode
setSec()) - Impossible de récupérer la bibliothèque d'albums de l'utilisateur
4. ❌ Méthode setSec() manquante
Python :
def setSec(self):
# Teste tous les secrets du spoofer
for value in self.spoofer.getSecrets().values():
self.s4 = value.encode("utf-8")
if self.userlib_getAlbums(sec=self.s4) is not None:
# Ce secret fonctionne !
return
Rust actuel :
- ❌ Méthode totalement absente
- ❌ Pas de mécanisme pour tester et sélectionner le bon secret
Impact :
- Si on utilise le Spoofer, impossible de trouver le bon secret parmi ceux retournés
5. ⚠️ Configuration incomplète
Python :
- Peut être initialisé avec
appid+configvalueOU utiliser le Spoofer
Rust actuel :
- ✅ Configuration du username/password via
QobuzConfigExt - ❌ Pas de configuration pour
appidetsecret/configvalue
Impact :
- Impossible de configurer manuellement un appID et secret valides
- Dépendance à un appID codé en dur qui peut devenir obsolète
Plan de correction
Phase 1: Extension de la configuration
Fichier: pmoqobuz/src/config_ext.rs
Ajouter au trait QobuzConfigExt :
get_qobuz_appid()/set_qobuz_appid()get_qobuz_secret()/set_qobuz_secret()(stocke la valeur base64)
Phase 2: Ajout du support du secret dans QobuzApi
Fichier: pmoqobuz/src/api/mod.rs
Modifications de QobuzApi :
pub struct QobuzApi {
client: Client,
app_id: String,
secret: Option<Vec<u8>>, // ← Nouveau : secret s4 décodé
user_auth_token: Option<String>,
user_id: Option<String>,
format_id: AudioFormat,
}
Nouvelles méthodes :
impl QobuzApi {
/// Crée une API avec appid + configvalue
pub fn with_secret(app_id: impl Into<String>, configvalue: &str) -> Result<Self>;
/// Crée une API en utilisant le Spoofer
pub async fn with_spoofer() -> Result<Self>;
/// Définit le secret s4
pub fn set_secret(&mut self, secret: Vec<u8>);
/// Teste un secret en appelant userlib_getAlbums
async fn test_secret(&self, secret: &[u8]) -> bool;
/// Teste et sélectionne le bon secret depuis le Spoofer
async fn set_secret_from_spoofer(&mut self, spoofer: &Spoofer) -> Result<()>;
}
Phase 3: Implémentation des méthodes signées
Fichier: pmoqobuz/src/api/signing.rs (nouveau)
use md5::{Md5, Digest};
use std::time::{SystemTime, UNIX_EPOCH};
/// Génère un timestamp Unix
pub fn get_timestamp() -> String {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs_f64()
.to_string()
}
/// Signe une requête track/getFileUrl
pub fn sign_track_get_file_url(
format_id: &str,
intent: &str,
track_id: &str,
timestamp: &str,
secret: &[u8],
) -> String {
let mut hasher = Md5::new();
hasher.update(b"trackgetFileUrlformat_id");
hasher.update(format_id.as_bytes());
hasher.update(b"intent");
hasher.update(intent.as_bytes());
hasher.update(b"track_id");
hasher.update(track_id.as_bytes());
hasher.update(timestamp.as_bytes());
hasher.update(secret);
format!("{:x}", hasher.finalize())
}
/// Signe une requête userLibrary/getAlbumsList
pub fn sign_userlib_get_albums(timestamp: &str, secret: &[u8]) -> String {
let mut hasher = Md5::new();
hasher.update(b"userLibrarygetAlbumsList");
hasher.update(timestamp.as_bytes());
hasher.update(secret);
format!("{:x}", hasher.finalize())
}
Fichier: pmoqobuz/src/api/catalog.rs
Modifier get_file_url :
pub async fn get_file_url(&self, track_id: &str) -> Result<StreamInfo> {
let format_id = self.format_id.id().to_string();
let timestamp = signing::get_timestamp();
// Signature MD5 requise !
let secret = self.secret.as_ref()
.ok_or_else(|| QobuzError::Configuration("Secret not configured".into()))?;
let signature = signing::sign_track_get_file_url(
&format_id,
"stream",
track_id,
×tamp,
secret,
);
let params = [
("track_id", track_id),
("format_id", format_id.as_str()),
("intent", "stream"),
("request_ts", timestamp.as_str()),
("request_sig", signature.as_str()),
];
let response: FileUrlResponse = self.get("/track/getFileUrl", ¶ms).await?;
// ...
}
Fichier: pmoqobuz/src/api/user.rs
Ajouter :
pub async fn get_user_albums(&self) -> Result<UserAlbumsResponse> {
let timestamp = signing::get_timestamp();
let secret = self.secret.as_ref()
.ok_or_else(|| QobuzError::Configuration("Secret not configured".into()))?;
let signature = signing::sign_userlib_get_albums(×tamp, secret);
let params = [
("app_id", self.app_id.as_str()),
("user_auth_token", self.user_auth_token.as_ref()
.ok_or_else(|| QobuzError::Unauthorized("Not logged in".into()))?
.as_str()),
("request_ts", timestamp.as_str()),
("request_sig", signature.as_str()),
];
self.post("/userLibrary/getAlbumsList", ¶ms).await
}
Phase 4: Modification de QobuzClient
Fichier: pmoqobuz/src/client.rs
impl QobuzClient {
/// Crée un client avec appID et secret depuis la config
pub async fn from_config() -> Result<Self> {
let config = pmoconfig::get_config();
// Essayer d'obtenir appid et secret depuis la config
let api = if let (Ok(appid), Ok(secret)) = (
config.get_qobuz_appid(),
config.get_qobuz_secret()
) {
QobuzApi::with_secret(appid, &secret)?
} else {
// Sinon, utiliser le Spoofer
warn!("AppID/secret not configured, using Spoofer");
QobuzApi::with_spoofer().await?
};
// Login...
let (username, password) = config.get_qobuz_credentials()?;
// ...
}
}
Dépendances à ajouter
Cargo.toml :
md5 = "0.7"
Résumé des fichiers à modifier/créer
Modifications
pmoqobuz/src/config_ext.rs- Ajouter appid et secretpmoqobuz/src/api/mod.rs- Ajouter champ secret et nouvelles méthodespmoqobuz/src/api/auth.rs- Appelerset_secret_from_spooferaprès loginpmoqobuz/src/api/catalog.rs- Ajouter signature àget_file_urlpmoqobuz/src/api/user.rs- Ajouterget_user_albumsavec signaturepmoqobuz/src/client.rs- Utiliser Spoofer si pas de configpmoqobuz/Cargo.toml- Ajouter dépendancemd5
Nouveaux fichiers
pmoqobuz/src/api/signing.rs- Fonctions de signature MD5
Tests nécessaires
- Test avec Spoofer : Vérifier que l'obtention automatique de l'appID fonctionne
- Test avec config manuelle : Vérifier qu'on peut configurer un appID/secret
- Test de signature : Vérifier que les signatures MD5 sont correctes
- Test de setSec : Vérifier que le bon secret est sélectionné
- Test de streaming : Vérifier qu'on obtient des URLs valides avec
get_file_url