use serde_json::Value;
use super::SunoClient;
use super::types::{
CreatePersonaRequest, EditPersonaRequest, PersonaClipsResponse, PersonaInfo,
PersonaListResponse, PersonaListScope, ProcessedClipInfo, TogglePersonaLoveResponse,
TrashPersonasRequest, TrashPersonasResponse,
};
use crate::core::CliError;
impl SunoClient {
pub async fn list_personas(
&self,
scope: PersonaListScope,
page: u32,
continuation_token: Option<&str>,
) -> Result<PersonaListResponse, CliError> {
let path = match scope {
PersonaListScope::Mine => "/api/persona/get-personas/",
PersonaListScope::Loved => "/api/persona/get-loved-personas/",
PersonaListScope::Followed => "/api/persona/get-followed-personas/",
};
self.with_auth_retry(|| async {
let mut query = vec![("page", page.to_string())];
if let Some(token) = continuation_token {
query.push(("continuation_token", token.to_string()));
}
let resp = self.get(path).query(&query).send().await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
pub async fn get_persona(&self, persona_id: &str) -> Result<PersonaInfo, CliError> {
self.with_auth_retry(|| async {
let resp = self
.get(&format!("/api/persona/get-persona/{persona_id}/"))
.send()
.await?;
let resp = self.check_response(resp).await?;
decode_persona(resp.json().await?)
})
.await
}
pub async fn get_persona_clips(
&self,
persona_id: &str,
page: u32,
) -> Result<PersonaClipsResponse, CliError> {
self.with_auth_retry(|| async {
let resp = self
.get(&format!("/api/persona/get-persona-paginated/{persona_id}/"))
.query(&[("page", page.to_string())])
.send()
.await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
pub async fn get_processed_clip(
&self,
processed_clip_id: &str,
) -> Result<ProcessedClipInfo, CliError> {
self.with_auth_retry(|| async {
let resp = self
.get(&format!("/api/processed_clip/{processed_clip_id}"))
.send()
.await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
pub async fn create_persona(
&self,
req: &CreatePersonaRequest,
) -> Result<PersonaInfo, CliError> {
self.with_auth_retry(|| async {
let resp = self.post("/api/persona/create/").json(req).send().await?;
let resp = self.check_response(resp).await?;
decode_persona(resp.json().await?)
})
.await
}
pub async fn edit_persona(&self, req: &EditPersonaRequest) -> Result<PersonaInfo, CliError> {
self.with_auth_retry(|| async {
let resp = self
.put(&format!("/api/persona/edit-persona/{}/", req.persona_id))
.json(req)
.send()
.await?;
let resp = self.check_response(resp).await?;
decode_persona(resp.json().await?)
})
.await
}
pub async fn toggle_persona_love(
&self,
persona_id: &str,
) -> Result<TogglePersonaLoveResponse, CliError> {
self.with_auth_retry(|| async {
let resp = self
.post(&format!("/api/persona/{persona_id}/toggle_love/"))
.send()
.await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
pub async fn set_persona_love(
&self,
persona_id: &str,
loved: bool,
) -> Result<TogglePersonaLoveResponse, CliError> {
let persona = self.get_persona(persona_id).await?;
if persona.is_loved == loved {
return Ok(TogglePersonaLoveResponse { loved });
}
self.toggle_persona_love(persona_id).await
}
pub async fn set_persona_visibility(
&self,
persona_id: &str,
is_public: bool,
) -> Result<PersonaInfo, CliError> {
self.with_auth_retry(|| async {
let resp = self
.put(&format!("/api/persona/set_visibility/{persona_id}/"))
.query(&[("is_public", is_public.to_string())])
.send()
.await?;
let resp = self.check_response(resp).await?;
decode_persona(resp.json().await?)
})
.await
}
pub async fn trash_personas(
&self,
persona_ids: &[String],
) -> Result<TrashPersonasResponse, CliError> {
self.update_persona_trash_state(persona_ids, false, false)
.await
}
pub async fn restore_personas(
&self,
persona_ids: &[String],
) -> Result<TrashPersonasResponse, CliError> {
self.update_persona_trash_state(persona_ids, true, false)
.await
}
pub async fn purge_personas(
&self,
persona_ids: &[String],
) -> Result<TrashPersonasResponse, CliError> {
self.update_persona_trash_state(persona_ids, false, true)
.await
}
async fn update_persona_trash_state(
&self,
persona_ids: &[String],
undo: bool,
hide: bool,
) -> Result<TrashPersonasResponse, CliError> {
self.with_auth_retry(|| async {
let req = TrashPersonasRequest {
persona_ids: persona_ids.to_vec(),
undo,
hide,
};
let resp = self
.put("/api/persona/bulk-trash-personas/")
.json(&req)
.send()
.await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
}
fn decode_persona(body: Value) -> Result<PersonaInfo, CliError> {
let candidates = [
body.get("persona").cloned(),
body.get("data").cloned(),
Some(body.clone()),
];
for candidate in candidates.into_iter().flatten() {
if let Ok(persona) = serde_json::from_value::<PersonaInfo>(candidate) {
return Ok(persona);
}
}
Err(CliError::Api {
code: "schema_drift",
message: format!("persona response did not match known Suno schema: {body}"),
})
}