on avance un peu pmoqobuz
This commit is contained in:
323
pmoqobuz/API_ANALYSIS.md
Normal file
323
pmoqobuz/API_ANALYSIS.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# 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 `Spoofer` pour obtenir l'appID et les secrets dynamiquement
|
||||
- Le `configvalue` est décodé et XORé avec l'appID pour obtenir le secret `s4`
|
||||
- Le secret `s4` est utilisé pour signer certaines requêtes critiques
|
||||
|
||||
**Rust actuel** :
|
||||
- ❌ Utilise un `DEFAULT_APP_ID` codé 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/getFileUrl` et `userLibrary/getAlbumsList` échoueront probablement car elles nécessitent une signature MD5
|
||||
|
||||
### 2. ❌ Signature MD5 des requêtes manquante
|
||||
|
||||
**Python - track_getFileUrl** :
|
||||
```python
|
||||
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)** :
|
||||
```rust
|
||||
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** :
|
||||
```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** :
|
||||
```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` + `configvalue` OU utiliser le Spoofer
|
||||
|
||||
**Rust actuel** :
|
||||
- ✅ Configuration du username/password via `QobuzConfigExt`
|
||||
- ❌ Pas de configuration pour `appid` et `secret`/`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` :
|
||||
```rust
|
||||
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 :
|
||||
```rust
|
||||
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)**
|
||||
|
||||
```rust
|
||||
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` :
|
||||
```rust
|
||||
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 :
|
||||
```rust
|
||||
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`**
|
||||
|
||||
```rust
|
||||
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** :
|
||||
```toml
|
||||
md5 = "0.7"
|
||||
```
|
||||
|
||||
## Résumé des fichiers à modifier/créer
|
||||
|
||||
### Modifications
|
||||
- [x] `pmoqobuz/src/config_ext.rs` - Ajouter appid et secret
|
||||
- [ ] `pmoqobuz/src/api/mod.rs` - Ajouter champ secret et nouvelles méthodes
|
||||
- [ ] `pmoqobuz/src/api/auth.rs` - Appeler `set_secret_from_spoofer` après login
|
||||
- [ ] `pmoqobuz/src/api/catalog.rs` - Ajouter signature à `get_file_url`
|
||||
- [ ] `pmoqobuz/src/api/user.rs` - Ajouter `get_user_albums` avec signature
|
||||
- [ ] `pmoqobuz/src/client.rs` - Utiliser Spoofer si pas de config
|
||||
- [ ] `pmoqobuz/Cargo.toml` - Ajouter dépendance `md5`
|
||||
|
||||
### Nouveaux fichiers
|
||||
- [ ] `pmoqobuz/src/api/signing.rs` - Fonctions de signature MD5
|
||||
|
||||
## Tests nécessaires
|
||||
|
||||
1. **Test avec Spoofer** : Vérifier que l'obtention automatique de l'appID fonctionne
|
||||
2. **Test avec config manuelle** : Vérifier qu'on peut configurer un appID/secret
|
||||
3. **Test de signature** : Vérifier que les signatures MD5 sont correctes
|
||||
4. **Test de setSec** : Vérifier que le bon secret est sélectionné
|
||||
5. **Test de streaming** : Vérifier qu'on obtient des URLs valides avec `get_file_url`
|
||||
Reference in New Issue
Block a user