use regex_lite::Regex;
use uuid::Uuid;
use veil::Redact;
use crate::{
arl::Arl,
decrypt::{Key, KEY_LENGTH},
error::{Error, Result},
http,
protocol::connect::{DeviceType, Percentage},
};
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Redact)]
pub enum Credentials {
Login {
email: String,
#[redact]
password: String,
},
#[redact(all)]
Arl(Arl),
}
#[derive(Clone, PartialEq, PartialOrd, Debug)]
pub struct Config {
pub app_name: String,
pub app_version: String,
pub app_lang: String,
pub device_name: String,
pub device_type: DeviceType,
pub device_id: Uuid,
pub normalization: bool,
pub initial_volume: Option<Percentage>,
pub interruptions: bool,
pub hook: Option<String>,
pub client_id: usize,
pub user_agent: String,
pub credentials: Credentials,
pub bf_secret: Option<Key>,
pub eavesdrop: bool,
}
impl Config {
pub const BF_SECRET_MD5: &'static str = "7ebf40da848f4a0fb3cc56ddbe6c2d09";
const WEB_PLAYER_URL: &'static str = "https://www.deezer.com/en/channels/explore/";
#[expect(clippy::missing_panics_doc)]
pub async fn try_key(client: &http::Client) -> Result<Key> {
let source = Self::get_text(client, Self::WEB_PLAYER_URL).await?;
let re = Regex::new(r"https:\/\/.+\/app-web.*\.js").unwrap();
let url = re
.find(&source)
.ok_or(Error::not_found("unable to find app-web source"))?;
let url = url.as_str();
trace!("bootstrapping from {url}");
let source = Self::get_text(client, url).await?;
let re = Regex::new(r"0x61%2C(0x[0-9a-f]{2}%2C){6}0x67").unwrap();
let a = re
.find(&source)
.ok_or(Error::not_found("unable to find first half of secret key"))?;
let re = Regex::new(r"0x31%2C(0x[0-9a-f]{2}%2C){6}0x34").unwrap();
let b = re
.find(&source)
.ok_or(Error::not_found("unable to find second half of secret key"))?;
let a = Self::convert_half(a.as_str())?;
let b = Self::convert_half(b.as_str())?;
let mut key = Vec::with_capacity(KEY_LENGTH);
for i in 0..(KEY_LENGTH / 2) {
key.push(a[i]);
key.push(b[i]);
}
let key = String::from_utf8_lossy(&key).into_owned();
key.parse()
}
async fn get_text(client: &http::Client, url: &str) -> Result<String> {
let url = url.parse::<reqwest::Url>()?;
let request = client.get(url, "");
let response = client.execute(request).await?;
response.text().await.map_err(Into::into)
}
fn convert_half(half: &str) -> Result<Vec<u8>> {
let bytes: Vec<u8> = half
.split("%2C")
.collect::<Vec<_>>()
.into_iter()
.rev()
.filter_map(|s| u8::from_str_radix(s.trim_start_matches("0x"), 16).ok())
.collect();
let len = bytes.len();
if len != 8 {
return Err(Error::out_of_range(format!(
"half key has {len} valid characters"
)));
}
Ok(bytes)
}
}