sunox 0.0.9

Generate AI music from your terminal via direct Suno web workflows
use std::time::Duration;

use serde::Deserialize;
use tokio::time::sleep;

use crate::captcha::{CDP_HOST, CDP_PORT};
use crate::core::CliError;

#[derive(Debug, Deserialize)]
pub(in crate::captcha) struct Target {
    #[serde(rename = "type")]
    target_type: String,
    url: String,
    #[serde(rename = "webSocketDebuggerUrl")]
    pub(in crate::captcha) web_socket_debugger_url: String,
}

pub(in crate::captcha) async fn cdp_version() -> Result<serde_json::Value, CliError> {
    let url = format!("http://{CDP_HOST}:{CDP_PORT}/json/version");
    let resp = reqwest::Client::new()
        .get(&url)
        .timeout(Duration::from_secs(2))
        .send()
        .await
        .map_err(|e| CliError::Config(format!("CDP /json/version: {e}")))?;
    let version: serde_json::Value = resp
        .json()
        .await
        .map_err(|e| CliError::Config(format!("CDP json parse: {e}")))?;
    Ok(version)
}

async fn cdp_list() -> Result<Vec<Target>, CliError> {
    let url = format!("http://{CDP_HOST}:{CDP_PORT}/json/list");
    let resp = reqwest::Client::new()
        .get(&url)
        .timeout(Duration::from_secs(5))
        .send()
        .await
        .map_err(|e| CliError::Config(format!("CDP /json/list: {e}")))?;
    let list: Vec<Target> = resp
        .json()
        .await
        .map_err(|e| CliError::Config(format!("CDP json parse: {e}")))?;
    Ok(list)
}

pub(in crate::captcha) async fn find_or_create_suno_tab() -> Result<Target, CliError> {
    let targets = cdp_list().await?;
    if let Some(target) = targets.into_iter().find(|target| {
        target.target_type == "page"
            && !target.web_socket_debugger_url.is_empty()
            && !target.url.starts_with("chrome://")
    }) {
        return Ok(target);
    }

    let url = format!(
        "http://{CDP_HOST}:{CDP_PORT}/json/new?{}",
        urlencode("about:blank")
    );
    let resp = reqwest::Client::new()
        .put(&url)
        .timeout(Duration::from_secs(10))
        .send()
        .await
        .map_err(|e| CliError::Config(format!("CDP /json/new: {e}")))?;
    let target: Target = resp
        .json()
        .await
        .map_err(|e| CliError::Config(format!("CDP /json/new parse: {e}")))?;
    sleep(Duration::from_millis(800)).await;
    Ok(target)
}

fn urlencode(s: &str) -> String {
    s.replace(":", "%3A").replace("/", "%2F")
}