songrec 0.6.7

An open-source Shazam client for Linux, written in Rust.
use gettextrs::gettext;
use glib::source::Priority;
use rand::seq::SliceRandom;
use serde_json::{json, Value};
use soup::prelude::SessionExt;
use std::error::Error;
use std::time::SystemTime;
use uuid::Uuid;

use crate::core::fingerprinting::signature_format::DecodedSignature;
use crate::core::fingerprinting::user_agent::USER_AGENTS;

pub async fn recognize_song_from_signature(
    session: &soup::Session,
    signature: &DecodedSignature,
) -> Result<Value, Box<dyn Error>> {
    session.set_user_agent(USER_AGENTS.choose(&mut rand::thread_rng()).unwrap());

    let timestamp_ms = SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)?
        .as_millis();

    let post_data = json!({
        "geolocation": {
            "altitude": 300,
            "latitude": 45,
            "longitude": 2
        },
        "signature": {
            "samplems": (signature.number_samples as f32 / signature.sample_rate_hz as f32 * 1000.) as u32,
            "timestamp": timestamp_ms as u32,
            "uri": signature.encode_to_uri()?
        },
        "timestamp": timestamp_ms as u32,
        "timezone": "Europe/Paris"
    }).to_string();

    let uuid_1 = Uuid::new_v4().to_hyphenated().to_string().to_uppercase();
    let uuid_2 = Uuid::new_v4().to_hyphenated().to_string();

    let url = format!(
        "https://amp.shazam.com/discovery/v5/en/US/android/-/tag/{}/{}\
?sync=true\
&webv3=true\
&sampling=true\
&connected=\
&shazamapiversion=v3\
&sharehub=true\
&video=v3",
        uuid_1, uuid_2
    );

    let message = soup::Message::from_encoded_form("POST", &url, post_data.into())?;

    let headers = message.request_headers().unwrap();
    headers.append("Content-Language", "en_US");
    headers.set_content_type(Some("application/json"), None);

    let response = session
        .send_and_read_future(&message, Priority::DEFAULT)
        .await?;

    if message.status_code() == 429 {
        return Err(Box::new(std::io::Error::new(
            std::io::ErrorKind::QuotaExceeded,
            gettext("Your IP has been rate-limited").as_str(),
        )));
    }

    Ok(serde_json::from_slice(&response[..])?)
}

pub async fn obtain_raw_cover_image(
    session: soup::Session,
    url: &str,
) -> Result<Vec<u8>, Box<dyn Error>> {
    let message = soup::Message::new("GET", url)?;
    session.set_user_agent(USER_AGENTS.choose(&mut rand::thread_rng()).unwrap());
    let headers = message.request_headers().unwrap();
    headers.append("Content-Language", "en_US");

    let response = session
        .send_and_read_future(&message, Priority::DEFAULT)
        .await?;

    Ok(response[..].to_vec())
}