Begin implementing endpoints

This commit is contained in:
Gabriel Tofvesson 2023-02-27 02:33:11 +01:00
parent 29aa50d871
commit c25d621903
No known key found for this signature in database
GPG Key ID: 6F1345DF28EDA13E
12 changed files with 1583 additions and 3 deletions

1098
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,3 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bytes = "1.4.0"
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0.152", features = ["std", "derive"] }
tokio = { version = "1", features = ["full"] }

50
src/api/history.rs Normal file
View File

@ -0,0 +1,50 @@
use bytes::Bytes;
use crate::{elevenlabs_api::{ElevenLabsAPI, self}, model::{history::{HistoryItems, HistoryResponse}, error::{APIError, HTTPValidationError}}};
impl ElevenLabsAPI {
pub async fn download_history(&self, items: HistoryItems) -> Result<Bytes, APIError> {
let response = self.get(elevenlabs_api::history::GET::History)?.json(&items).send().await?;
if response.status().is_success() {
Ok(response.bytes().await?)
} else {
let error: HTTPValidationError = response.json().await?;
Err(APIError::HTTPError(error))
}
}
pub async fn delete_history(&self, item: String) -> Result<String, APIError> {
let response = self.delete(elevenlabs_api::history::DELETE::HistoryItem { item_id: item })?.send().await?;
if response.status().is_success() {
Ok(response.text().await?)
} else {
let error: HTTPValidationError = response.json().await?;
Err(APIError::HTTPError(error))
}
}
pub async fn get_history_audio(&self, item: String) -> Result<Bytes, APIError> {
let response = self.get(elevenlabs_api::history::GET::HistoryItem { item_id: item })?.send().await?;
if response.status().is_success() {
Ok(response.bytes().await?)
} else {
let error: HTTPValidationError = response.json().await?;
Err(APIError::HTTPError(error))
}
}
pub async fn get_history_items(&self) -> Result<HistoryResponse, APIError> {
let response = self.get(elevenlabs_api::history::GET::History)?.send().await?;
if response.status().is_success() {
let history_items: HistoryResponse = response.json().await?;
Ok(history_items)
} else {
let error: HTTPValidationError = response.json().await?;
Err(APIError::HTTPError(error))
}
}
}

2
src/api/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod user;
pub mod history;

30
src/api/user.rs Normal file
View File

@ -0,0 +1,30 @@
use crate::model::user::User;
use crate::model::user::subscription::Subscription;
use crate::model::error::{HTTPValidationError, APIError};
use crate::elevenlabs_api::{ElevenLabsAPI, self};
impl ElevenLabsAPI {
pub async fn get_subscription(&self) -> Result<Subscription, APIError> {
let response = self.get(elevenlabs_api::user::GET::Subscription)?.send().await?;
if response.status().is_success() {
let subscription: Subscription = response.json().await?;
Ok(subscription)
} else {
let error: HTTPValidationError = response.json().await?;
Err(APIError::HTTPError(error))
}
}
pub async fn get_user_info(&self) -> Result<User, APIError> {
let response = self.get(elevenlabs_api::user::GET::User)?.send().await?;
if response.status().is_success() {
let user: User = response.json().await?;
Ok(user)
} else {
let error: HTTPValidationError = response.json().await?;
Err(APIError::HTTPError(error))
}
}
}

220
src/elevenlabs_api.rs Normal file
View File

@ -0,0 +1,220 @@
use reqwest::{Response, Error, Client, RequestBuilder};
#[derive(Debug)]
pub struct ElevenLabsAPI {
pub host: &'static str,
pub api_key: String,
}
impl ElevenLabsAPI {
pub fn new(api_key: String) -> Self {
Self {
host: "https://api.elevenlabs.io",
api_key,
}
}
pub fn endpoint(&self, endpoint: impl Endpoint) -> String {
format!("{}{}", self.host, endpoint.path())
}
fn with_header(&self, builder: RequestBuilder) -> RequestBuilder {
builder.header("xi-api-key", self.api_key.clone())
}
pub fn get(&self, endpoint: impl Endpoint) -> Result<RequestBuilder, Error> {
Ok(self.with_header(Client::builder().build()?.get(self.endpoint(endpoint))))
}
pub fn post(&self, endpoint: impl Endpoint) -> Result<RequestBuilder, Error> {
Ok(self.with_header(Client::builder().build()?.post(self.endpoint(endpoint))))
}
pub fn delete(&self, endpoint: impl Endpoint) -> Result<RequestBuilder, Error> {
Ok(self.with_header(Client::builder().build()?.delete(self.endpoint(endpoint))))
}
}
pub trait Endpoint {
fn path(&self) -> String;
}
pub mod tts {
use super::Endpoint;
pub enum POST {
File {
voice_id: String,
},
Stream {
voice_id: String,
},
}
impl Endpoint for POST {
fn path(&self) -> String {
match self {
POST::File { voice_id } => format!("/v1/text-to-speech/{}", voice_id),
POST::Stream { voice_id } => format!("/v1/text-to-speech/{}/stream", voice_id),
}
}
}
}
pub mod voices {
use super::Endpoint;
pub enum GET {
List,
DefaultSettings,
Settings {
voice_id: String,
},
Voice {
voice_id: String,
},
}
impl Endpoint for GET {
fn path(&self) -> String {
match self {
GET::List => "/v1/voices".to_string(),
GET::DefaultSettings => "/v1/voices/settings/default".to_string(),
GET::Settings { voice_id } => format!("/v1/voices/{}/settings", voice_id),
GET::Voice { voice_id } => format!("/v1/voices/{}", voice_id),
}
}
}
pub enum DELETE {
Voice {
voice_id: String,
},
}
impl Endpoint for DELETE {
fn path(&self) -> String {
match self {
DELETE::Voice { voice_id } => format!("/v1/voices/{}", voice_id),
}
}
}
pub enum POST {
EditSettings {
voice_id: String,
},
AddVoice,
EditVoice {
voice_id: String,
},
}
impl Endpoint for POST {
fn path(&self) -> String {
match self {
POST::EditSettings { voice_id } => format!("/v1/voices/{}/settings/edit", voice_id),
POST::AddVoice => "/v1/voices".to_string(),
POST::EditVoice { voice_id } => format!("/v1/voices/{}", voice_id),
}
}
}
}
pub mod samples {
use super::Endpoint;
pub enum GET {
Sample {
voice_id: String,
sample_id: String,
},
}
impl Endpoint for GET {
fn path(&self) -> String {
match self {
GET::Sample { voice_id, sample_id } => format!("/v1/voices/{voice_id}/samples/{sample_id}/audio"),
}
}
}
pub enum DELETE {
Sample {
voice_id: String,
sample_id: String,
},
}
impl Endpoint for DELETE {
fn path(&self) -> String {
match self {
DELETE::Sample { voice_id, sample_id } => format!("/v1/voices/{voice_id}/samples/{sample_id}"),
}
}
}
}
pub mod history {
use super::Endpoint;
pub enum GET {
History,
HistoryItem {
item_id: String,
},
}
impl Endpoint for GET {
fn path(&self) -> String {
match self {
GET::History => "/v1/history".to_string(),
GET::HistoryItem { item_id } => format!("/v1/history/{item_id}/audio"),
}
}
}
pub enum DELETE {
HistoryItem {
item_id: String,
},
}
impl Endpoint for DELETE {
fn path(&self) -> String {
match self {
DELETE::HistoryItem { item_id } => format!("/v1/history/{item_id}"),
}
}
}
pub enum POST {
DownloadHistory
}
impl Endpoint for POST {
fn path(&self) -> String {
match self {
POST::DownloadHistory => "/v1/history/download".to_string(),
}
}
}
}
pub mod user {
use super::Endpoint;
pub enum GET {
Subscription,
User,
}
impl Endpoint for GET {
fn path(&self) -> String {
match self {
GET::Subscription => "/v1/user/subscription".to_string(),
GET::User => "/v1/user".to_string(),
}
}
}
}

35
src/lib.rs Normal file
View File

@ -0,0 +1,35 @@
pub mod model;
pub mod api;
pub mod elevenlabs_api;
extern crate reqwest;
#[cfg(test)]
mod tests {
use super::*;
fn get_api() -> elevenlabs_api::ElevenLabsAPI {
elevenlabs_api::ElevenLabsAPI::new(std::fs::read_to_string(std::path::Path::new("apikey.txt")).unwrap().trim().to_string())
}
#[tokio::test]
async fn get_user_info() {
let api = get_api();
let result = api.get_user_info().await;
assert!(result.is_ok());
let user = result.unwrap();
assert_eq!(user.xi_api_key, api.api_key);
}
#[tokio::test]
async fn get_history_items() {
let api = get_api();
let result = api.get_history_items().await;
assert!(result.is_ok());
println!("{:?}", result.unwrap());
}
}

View File

@ -1,3 +0,0 @@
fn main() {
println!("Hello, world!");
}

39
src/model/error.rs Normal file
View File

@ -0,0 +1,39 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub enum Location {
StringLocation(String),
NumberLocation(u32),
}
#[derive(Debug, Deserialize)]
pub struct ValidationError {
loc: Vec<Location>,
msg: String,
#[serde(rename = "type")]
error_type: String,
}
#[derive(Debug, Deserialize)]
pub struct HTTPValidationError {
detail: Vec<ValidationError>,
}
#[derive(Debug)]
pub enum APIError {
HTTPError(HTTPValidationError),
NetworkError(reqwest::Error),
}
impl From<reqwest::Error> for APIError {
fn from(error: reqwest::Error) -> Self {
Self::NetworkError(error)
}
}
impl From<HTTPValidationError> for APIError {
fn from(error: HTTPValidationError) -> Self {
Self::HTTPError(error)
}
}

37
src/model/history.rs Normal file
View File

@ -0,0 +1,37 @@
use serde::{Serialize, Deserialize};
#[derive(Debug, Deserialize)]
pub enum State {
#[serde(rename = "created")]
Created,
#[serde(rename = "deleted")]
Deleted,
#[serde(rename = "processing")]
Processing,
}
#[derive(Debug, Deserialize)]
pub struct Settings;
#[derive(Debug, Deserialize)]
pub struct Item {
history_item_id: String,
voice_id: String,
text: String,
date_unix: u64,
character_count_change_from: u32,
character_count_change_to: u32,
content_type: String,
state: State,
//settings: Settings,
}
#[derive(Debug, Deserialize)]
pub struct HistoryResponse {
history: Vec<Item>,
}
#[derive(Debug, Serialize)]
pub struct HistoryItems {
history_item_ids: Vec<String>,
}

3
src/model/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod user;
pub mod error;
pub mod history;

65
src/model/user.rs Normal file
View File

@ -0,0 +1,65 @@
use serde::Deserialize;
pub mod subscription {
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Subscription {
pub tier: String,
pub character_count: u32,
pub character_limit: u32,
pub can_extend_character_limit: bool,
pub allowed_to_extend_character_limit: bool,
pub next_character_count_reset_unix: u64,
pub voice_limit: u32,
pub can_extend_voice_limit: bool,
pub can_use_instant_voice_cloning: bool,
pub available_models: Vec<TTSModel>,
}
#[derive(Debug, Deserialize)]
pub struct TTSModel {
pub model_id: String,
pub display_name: String,
pub supported_languages: Vec<Language>,
}
#[derive(Debug, Deserialize)]
pub struct Language {
pub iso_code: String,
pub display_name: String,
}
#[derive(Debug, Deserialize)]
pub enum Status {
#[serde(rename = "trialing")]
Trialing,
#[serde(rename = "active")]
Active,
#[serde(rename = "past_due")]
Incomplete,
#[serde(rename = "incomplete")]
IncompleteExpired,
#[serde(rename = "incomplete_expired")]
PastDue,
#[serde(rename = "canceled")]
Canceled,
#[serde(rename = "unpaid")]
Unpaid,
#[serde(rename = "free")]
Free,
}
#[derive(Debug, Deserialize)]
pub struct Invoice {
pub amount_due_cents: u32,
pub next_payment_attempt_unix: u64,
}
}
#[derive(Debug, Deserialize)]
pub struct User {
pub subscription: subscription::Subscription,
pub is_new_user: bool,
pub xi_api_key: String,
}