First functional version

This commit is contained in:
VC
2023-09-29 18:10:10 +02:00
parent 2a3ebf6fe4
commit e664853728
8 changed files with 304 additions and 5 deletions

167
src/youtube.rs Normal file
View File

@@ -0,0 +1,167 @@
use crate::{config::YoutubeConfig, error::TootubeError, peertube::PeerTubeVideo};
use serde::{Deserialize, Serialize};
use std::{error::Error, sync::Mutex};
static ACCESS_TOKEN: Mutex<String> = Mutex::new(String::new());
#[derive(Serialize, Debug)]
struct RefreshTokenRequest {
refresh_token: String,
client_id: String,
client_secret: String,
redirect_uri: String,
grant_type: String,
}
impl Default for RefreshTokenRequest {
fn default() -> Self {
RefreshTokenRequest {
refresh_token: "".to_string(),
client_id: "".to_string(),
client_secret: "".to_string(),
redirect_uri: "urn:ietf:wg:oauth:2.0:oob".to_string(),
grant_type: "refresh_token".to_string(),
}
}
}
#[derive(Deserialize, Debug)]
struct AccessTokenResponse {
access_token: String,
}
#[derive(Serialize, Debug)]
struct YoutubeUploadParams {
snippet: YoutubeUploadParamsSnippet,
status: YoutubeUploadParamsStatus,
}
#[derive(Serialize, Debug)]
struct YoutubeUploadParamsSnippet {
title: String,
description: String,
#[serde(rename = "categoryId")]
category_id: String,
#[serde(rename = "defaultAudioLanguage")]
default_audio_language: String,
tags: Option<Vec<String>>,
}
impl Default for YoutubeUploadParamsSnippet {
fn default() -> Self {
YoutubeUploadParamsSnippet {
title: "".to_string(),
description: "".to_string(),
category_id: "20".to_string(),
default_audio_language: "fr".to_string(),
tags: None,
}
}
}
#[derive(Serialize, Debug)]
struct YoutubeUploadParamsStatus {
#[serde(rename = "selfDeclaredMadeForKids")]
self_declared_made_for_kids: bool,
#[serde(rename = "privacyStatus")]
privacy_status: String,
license: String,
}
impl Default for YoutubeUploadParamsStatus {
fn default() -> Self {
YoutubeUploadParamsStatus {
self_declared_made_for_kids: false,
privacy_status: "public".to_string(),
license: "creativeCommon".to_string(),
}
}
}
/// Ensures that Token has been refreshed and that it is unique
fn refresh_token(config: &YoutubeConfig) -> Result<String, Box<dyn Error>> {
if let Ok(mut unlocked_access_token) = ACCESS_TOKEN.lock() {
if unlocked_access_token.is_empty() {
let refresh_token = RefreshTokenRequest {
refresh_token: config.refresh_token.clone(),
client_id: config.client_id.clone(),
client_secret: config.client_secret.clone(),
..Default::default()
};
let client = reqwest::blocking::Client::new();
let res = client
.post("https://accounts.google.com/o/oauth2/token")
.json(&refresh_token)
.send()?;
let access_token: AccessTokenResponse = res.json()?;
*unlocked_access_token = access_token.access_token.clone();
}
}
Ok(ACCESS_TOKEN.lock().unwrap().to_string())
}
pub fn create_resumable_upload(
config: &YoutubeConfig,
vid: &PeerTubeVideo,
) -> Result<String, Box<dyn Error>> {
let access_token = refresh_token(config)?;
let upload_params = YoutubeUploadParams {
snippet: {
YoutubeUploadParamsSnippet {
title: vid.name.clone(),
description: vid.description.clone(),
tags: vid.tags.clone(),
..Default::default()
}
},
status: {
YoutubeUploadParamsStatus {
..Default::default()
}
},
};
let client = reqwest::blocking::Client::new();
let res = client.post("https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet%2Cstatus")
.header("Authorization", format!("Bearer {}", access_token))
.json(&upload_params)
.send()?;
if res.status().is_success() {
Ok(res
.headers()
.get("x-guploader-uploadid")
.ok_or("Cannot find suitable header")?
.to_str()?
.to_string())
} else {
Err(TootubeError::new("Cannot create resumable upload!").into())
}
}
pub fn upload_video(
f_path: &str,
r_id: &str,
config: &YoutubeConfig,
) -> Result<(), Box<dyn Error>> {
let access_token = refresh_token(config)?;
let client = reqwest::blocking::Client::new();
let res = client.put(format!("https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet%2Cstatus&upload_id={}", r_id))
.header("Authorization", format!("Bearer {}", access_token))
.body(f_path.to_string())
.send()?;
if res.status().is_success() {
Ok(())
} else {
Err(TootubeError::new(&format!("Cannot upload video: {:?}", res.text())).into())
}
}