use crate::{
client::{WayclipClient, WayclipResponse},
models::{
clips::{
games::ClipsGames,
hosted::{
ClipVisibility, ClipsNewMetadata, ClipsResponse, HostedClip,
PatchClipsClipIdRequest,
},
tags::ClipsTag,
},
error::WayclipError,
nutype::ClipNameSanitised,
query::{FullQueryWeb, PaginatedResponseWeb},
},
settings::output::VideoFormat,
};
use itertools::Itertools;
use reqwest::{Method, multipart};
use serde_json::json;
use std::{mem::discriminant, path::PathBuf, sync::Arc};
use tokio::fs;
#[derive(Clone)]
pub struct ClipsHttpClient {
client: WayclipClient,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PatchClipField {
Visibility(ClipVisibility),
Name(String),
Tags(Vec<ClipsTag>),
Game(Option<ClipsGames>),
}
impl ClipsHttpClient {
pub fn new(api_url: url::Url) -> Result<Self, WayclipError> {
let client = WayclipClient::new(api_url)?;
Ok(Self { client })
}
pub async fn query_me_clips(
&mut self,
query: FullQueryWeb,
) -> Result<PaginatedResponseWeb<HostedClip>, WayclipError> {
let response: WayclipResponse<PaginatedResponseWeb<ClipsResponse>> = self
.client
.with_credentials()
.await?
.with_body(&query)
.await?
.send_call(Method::POST, "/users/me/clips/search")
.await?;
response.into_inner()
}
pub async fn delete(&mut self, clip_id: &str) -> Result<(), WayclipError> {
self.client
.with_credentials()
.await?
.send_call::<()>(Method::DELETE, &format!("/clips/{}", clip_id))
.await?;
Ok(())
}
pub async fn patch(
&mut self,
new_values: Vec<PatchClipField>,
clip_id: &str,
) -> Result<(), WayclipError> {
if new_values.is_empty() || !new_values.iter().map(discriminant).all_unique() {
return Err(WayclipError::Validation(
"Invalid new_values parameter".into(),
));
}
let mut name = None;
let mut tags = None;
let mut game = None;
let mut visibility = None;
for value in new_values {
match value {
PatchClipField::Name(n) => name = Some(n),
PatchClipField::Tags(t) => tags = Some(t),
PatchClipField::Visibility(v) => visibility = Some(v),
PatchClipField::Game(g) => game = Some(g),
}
}
let body = PatchClipsClipIdRequest {
name: name.map(ClipNameSanitised::try_from).transpose()?,
tags: tags
.map(|ts| ts.into_iter().map(TryInto::try_into).collect())
.transpose()?,
detected_game: game,
clip_visibility: visibility,
comment_visibility: None,
};
let _response: WayclipResponse<ClipsResponse> = self
.client
.with_credentials()
.await?
.with_body(&body)
.await?
.send_call(Method::PATCH, &format!("/clips/{}", clip_id))
.await?;
Ok(())
}
pub async fn upload(
&mut self,
video_format: VideoFormat,
video_path: &PathBuf,
metadata: ClipsNewMetadata,
) -> Result<HostedClip, WayclipError> {
let bytes = Arc::new(fs::read(video_path).await?);
let file_name = metadata.name.clone().into_inner();
let mime_string = video_format.get_mime_str().to_string();
let json_string = json!(metadata).to_string();
let multipart_builder = Arc::new(move || {
let file_part = multipart::Part::bytes((*bytes).clone())
.file_name(file_name.clone())
.mime_str(&mime_string)
.expect("valid mime type");
let json_part = multipart::Part::text(json_string.clone())
.mime_str("application/json")
.expect("valid mime type");
multipart::Form::new()
.part("json", json_part)
.part("file", file_part)
});
let response: WayclipResponse<HostedClip> = self
.client
.with_credentials()
.await?
.with_multipart(multipart_builder)
.await
.send_call(Method::POST, "clips")
.await?;
log::info!("Uploaded clip successfully");
response.into_inner()
}
}